Reputation: 58
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
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
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