Nikolaus Trixner
Nikolaus Trixner

Reputation: 58

JAXB/XSD: Number instead of Element name

I have to create Java Objects from an xml file (that I can not edit before conversion), so I thought that it would probably be the best to create an XSD and then Unmarshall the file via JAXB. The problem is, there are some elements in that XML that are formatted like this:

<parent-element>
    <id-00001>...</id-00001>
    <id-00002>...</id-00002>
    ...
</parent-element>

So basically I have a list of xml-elements, with the "name" of the element being the order inside of the list. There is a potentially unlimited number of elements in each of those lists. Is it possible to unmarshall this with JAXB or do I HAVE TO use an XML-Reader and loop through the elements? The "id-XXXXX" elements are all formatted in the same way internally, so if it would be formatted like "" it would be possible without any issues.

Thanks in advance!

Upvotes: 0

Views: 410

Answers (2)

Michał Ziober
Michał Ziober

Reputation: 38635

I guess that your XML payload looks like below:

<root>
    <parent-element>
        <id-00001>value 1</id-00001>
        <id-00002>value 2</id-00002>
    </parent-element>
</root>

To read nodes with dynamic names we need to write custom adapter (javax.xml.bind.annotation.adapters.XmlAdapter). Assume that model looks like below:

@XmlRootElement(name = "root")
@XmlAccessorType(XmlAccessType.FIELD)
class Root {

    @XmlElement(name = "parent-element")
    @XmlJavaTypeAdapter(IdsXmlAdapter.class)
    private MultiItemList list;

    // getters, setters, toString
}

class MultiItemList {

    private List<String> ids;

    // getters, setters, toString
}

For above XML and POJO model custom adapter could look like below:

class IdsXmlAdapter extends XmlAdapter<Object, MultiItemList> {

    @Override
    public MultiItemList unmarshal(Object v) {
        Element element = (Element) v;
        NodeList childNodes = element.getChildNodes();
        List<String> ids = new ArrayList<>(childNodes.getLength());
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node item = childNodes.item(i);
            if (item.getNodeName().equals("#text")) {
                continue;
            }
            ids.add(item.getTextContent());
        }

        MultiItemList multiItemList = new MultiItemList();
        multiItemList.setIds(ids);
        return multiItemList;
    }

    @Override
    public Object marshal(MultiItemList v) throws Exception {
        return null; // Implement if needed
    }
}

Example usage:

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;

public class JaxbApp {

    public static void main(String[] args) throws Exception {
        File xmlFile = new File("./resource/test.xml").getAbsoluteFile();

        JAXBContext jaxbContext = JAXBContext.newInstance(Root.class);

        Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(new FileReader(xmlFile));
        System.out.println(unmarshal);
    }
}

prints:

Root{list=Root{ids=[value 1, value 2]}}

Notice, that you can not implement directly XmlAdapter<Element, MultiItemList> adapter because of exception:

Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
org.w3c.dom.Element is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at org.w3c.dom.Element

See also:

Upvotes: 1

martidis
martidis

Reputation: 2975

Since your xml has potentially an unlimited number of "id-elements", it is tricky to create matching POJO structures. You can use @XmlAnyElement and an adapter. @XmlAnyElement will read the varying elements as dom nodes and you can process them in your adapter.

For example, with the xml given below:

<parent-element>
    <id-00001>value 1</id-00001>
    <id-00002>value 2</id-00002>
</parent-element>

Your root could look like this:

@XmlRootElement(name = "parent-element")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {

    @XmlAnyElement
    @XmlJavaTypeAdapter(MyAdapter.class)
    private List<String> varyingElements;

}

and your adapter like this:

public class MyAdapter extends XmlAdapter<Element, String> {

    @Override
    public Element marshal(String arg0) throws Exception {
        // do what you must
        return null;
    }

    @Override
    public String unmarshal(Element element) throws Exception {
        return element.getChildNodes().item(0).getNodeValue();
    }

}

Element as in org.w3c.dom.Element

Upvotes: 1

Related Questions