Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - deserializing a flattened XML file. (XStream, JAXB, MOXy, whichever...)

I'm currently using XStream to parse an XML file but can't get it to do what I need it to do. I will change to another library if necessary, whatever can solve this problem!

Basically I'm trying to parse an XML feed similar to this:

<product>
  <title>Transformers Best of Grimlock</title>
  <author1>Bob Budiansky</author1>
  <author2>Simon Furman</author2>
</product>

Which I'm trying to parse into a model like this:

public class Product extends Model {

  public String title;

  public List<String> authors;

}

The title works beautifully but I'm having difficulty parsing the authors. Unfortunately getting the XML feed in a "more sensible" format like this is not an option:

...
<authors>
  <author>Bob Budiansky</author>
  <author>Simon Furman</author>
</authors>
...

Is there anything I can do to parse "author1", "author2", etc into a list?

Please bear in mind I'm very new to XStream (and haven't used JAXB at all!) so will need pointers on where to start if I've to write any custom binders or whatever.

Thanks for reading, I look forward to any potential solutions that may save my sanity! :)


Edit: I've updated the post to show I'm not tied to XStream. Really struggling to find the answer to this - perhaps I just don't know what to search for...

like image 452
Chris Waugh Avatar asked Dec 11 '25 12:12

Chris Waugh


2 Answers

The following approach should work with any XML-binding library that can unmarshal from a StAX XMLStreamReader. I'll demonstrate below using the standard JAXB (JSR-222) APIs. (Note: I'm the EclipseLink JAXB (MOXy) lead).

Product

We'll annotate the model as if the authors property is mapped to the author element.

package forum11666565;

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product extends Model {

    public String title;

    @XmlElement(name = "author")
    public List<String> authors;

}

Demo

We'll parse the XML document with an XMLStreamReader. On that XMLStreamReader we'll create a StreamReaderDelegate that will report any elements starting with author as an element called author.

package forum11666565;

import java.io.FileInputStream;
import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.stream.util.StreamReaderDelegate;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Product.class);

        XMLInputFactory xif = XMLInputFactory.newFactory();
        XMLStreamReader xsr = xif.createXMLStreamReader(new FileInputStream("src/forum11666565/input.xml"));
        xsr = new StreamReaderDelegate(xsr) {

            @Override
            public String getLocalName() {
                String localName = super.getLocalName();
                if(localName.startsWith("author")) {
                    return "author";
                } else {
                    return localName;
                }
            }

        };

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Product product = (Product) unmarshaller.unmarshal(xsr);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(product, System.out);
    }

}

input.xml

<product>
  <title>Transformers: Best of Grimlock</title>
  <author1>Bob Budiansky</author1>
  <author2>Simon Furman</author2>
</product>

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product>
    <title>Transformers: Best of Grimlock</title>
    <author>Bob Budiansky</author>
    <author>Simon Furman</author>
</product>
like image 82
bdoughan Avatar answered Dec 14 '25 02:12

bdoughan


You could transform the initial XML via XSLT before parsing it with XStream:

The XSLT stylesheet would transform the XML as you want:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="product">
        <xsl:element name="product">
            <xsl:for-each select="*[not(starts-with(name(.), 'author'))]">
                <xsl:copy-of select="." />
            </xsl:for-each>
            <xsl:element name="authors">
                <xsl:for-each select="*[starts-with(name(.), 'author')]">
                    <xsl:element name="author">
                        <xsl:value-of select="."/>
                    </xsl:element>
                </xsl:for-each>
            </xsl:element>
        </xsl:element>
    </xsl:template>

    <xsl:template match="*">
        <xsl:copy select=".">
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
like image 25
beny23 Avatar answered Dec 14 '25 01:12

beny23



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!