JAXB/XSD: Number instead of Element name - java

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!

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:
Read XML body element having multiple values based on different requests

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

Related

Detect object type during deserialization based on xml document properties in Jackson - no JsonNode

I have an XML document that I want to deserialise into an object of a class that I do not know during compilation and the type is determined by both the root element name of the document and a type element text content. The problem is twofold:
I have element lists in my XML document so I can't parse it to JsonNode/Map because in the best case scenario it will only provide me with the last value on the list, the information is lost in the process. All the solutions I found rely on JsonNode API (like convertValue or treeToValue).
I can't use JsonTypeInfo - I don't know the concrete classes that will be deserialised and so I cannot use the annotations. Also I don't think JsonTypeInfo is this flexible as to include both root element name and a property value.
I tried using a custom deserialiser that would wrap the actual parsed JsonNode into another Object with rootElement as an only key (by default this root element is not included in the parsed document). After that I deserialise the document, get the root element from the node, then take the type element value, combine the two, create the name of the class and then convert the JsonNode to the final object. The problem with this approach is that it doesn't handle the lists well and either throws and exception of invalid mapping or provides me with the last element of the list depending of how I write my model or configure the mapper.
package zm.study.xmlserialize.jackson;
import static org.junit.Assert.assertEquals;
import java.io.StringReader;
import java.util.List;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser;
public class JacksonListTest2 {
public static class A < D extends AData > {
public String type;
public D data;
}
public static class AData {}
public static class BRequestData extends AData {
#JacksonXmlElementWrapper(useWrapping = false)
public List < String > bs;
}
public static class BRequest extends A {}
#Test
public void test() throws Exception {
String xml;
xml = "<Request><type>B</type><data><bs>1</bs><bs>2</bs></data></Request>";
XmlMapper mapper = new XmlMapper();
mapper.registerModule(
new SimpleModule()
.addDeserializer(JsonNode.class, new JsonNodeDeserializer() {
#Override
public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws java.io.IOException {
String rootName = ((FromXmlParser) p).getStaxReader().getLocalName();
return ctxt.getNodeFactory().objectNode().set(rootName, super.deserialize(p, ctxt));
};
})
);
JsonNode rootNode = mapper.readTree(new StringReader(xml));
String rootName = rootNode.fields().next().getKey();
JsonNode contentNode = rootNode.get(rootName);
String type = contentNode.get("type").asText();
String className = getClass().getCanonicalName() + "$" + type + rootName;
BRequest value = (BRequest) mapper.convertValue(contentNode, Class.forName(className));
System.out.println(value.data.bs);
assertEquals(2, value.data.bs.size());
}
}
My approach results in throwing exception:
Cannot deserialize instance of java.util.ArrayList out of
VALUE_STRING token
I also tried creating a custom deserialiser parsing just enough of the document to find out what type I'm dealing with and then delegating the rest of the actual parsing (the data element) to the default deserialiser but didn't manage to write any sensible code to do it.
I think I need a new approach, any ideas?

Unmarshalling a simple XML

I am having hard time unmarshalling the following XML using JAXB. This is the xml which is having only one field with an attribute. I have referred many tutorials where they only do an example of reading a string in fields which s not helpful in my case.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<CircuitImpactConfigs id="1">
<Objects>
<Object type="1" impactAnalysisDataBuilderClassName="tttttttt"/>
<Object type="2" impactAnalysisDataBuilderClassName="yyyyyyyyy"/>
<Object type="3" impactAnalysisDataBuilderClassName="eeeeeee" />
<Object type="4" impactAnalysisDataBuilderClassName="iiiiiiiii"/>
<Object type="5" impactAnalysisDataBuilderClassName="rrrrrrrrrr"/>
<Object type="6" impactAnalysisDataBuilderClassName="zzzzzz"/>
<Object type="7" impactAnalysisDataBuilderClassName="qqqqqqqqqqq"/>
</Objects>
<ForceSwitchMode name="FORCE_SWITCHED" />
</CircuitImpactConfigs>
Based on what i learnt from tutorial
My Classes are CircuitImpactConfigs.java
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement(name = "CircuitImpactConfigs")
#XmlAccessorType(XmlAccessType.FIELD)
public class CircuitImpactConfigs {
private ForceSwitchMode ForceSwitchMode;
List<Obj> Object;
#XmlElement
public List<Obj> getObject() {
return Object;
}
public void setObject(List<Obj> object) {
Object = object;
}
#XmlElement
public ForceSwitchMode getForceSwitchMode() {
return ForceSwitchMode;
}
public void setForceSwitchMode(ForceSwitchMode forceSwitchMode) {
ForceSwitchMode = forceSwitchMode;
}
}
and
import javax.xml.bind.annotation.XmlAttribute;
public class ForceSwitchMode {
private String name;
#XmlAttribute
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and
import javax.xml.bind.annotation.XmlAttribute;
public class Obj {
String type;
String impactAnalysisDataBuilderClassName;
#XmlAttribute
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#XmlAttribute
public String getImpactAnalysisDataBuilderClassName() {
return impactAnalysisDataBuilderClassName;
}
public void setImpactAnalysisDataBuilderClassName(String impactAnalysisDataBuilderClassName) {
this.impactAnalysisDataBuilderClassName = impactAnalysisDataBuilderClassName;
}
}
I am getting null list when doing the unmarshalling. This is the class where i create the JAXBcontext object and create unmarshalling object.
import java.io.File;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class CircuitImpactConfUnmarshaller {
public static void main(String[] args) throws JAXBException {
File file = new File("CircuitImpact.xml");
System.out.println(file.exists());
JAXBContext jaxbContext = JAXBContext.newInstance(CircuitImpactConfigs.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
CircuitImpactConfigs que = (CircuitImpactConfigs) jaxbUnmarshaller.unmarshal(file);
System.out.println(que.getForceSwitchMode().getName());
List<Obj> list = que.getObject();
System.out.println(list);
}
}
the last print statement is giving null. I understand i am doing something wrong in the class Obj
JAXB uses implicit naming conventions and explicit annotations to define a mapping between a XML and a Java structure.
Either element and attribute names in the XML match field names in Java (match by naming convention) or you need to use annotations to establish the mapping.
The Java list CircuitImpactConfigs.Object is not getting filled because the mapping failed, since the corresponding element in the XML is named Objects.
You can now either rename CircuitImpactConfigs.Object to CircuitImpactConfigs.Objects or use the name parameter of a JAXB annotation to define the corresponding name:
#XmlElement(name="Objects")
public List<Obj> getObject() {
EDIT: As you indicate in your comments there are still other mapping issues with your code. I would suggest that you adapt another approach:
Create a CircuitImpactConfigs object with all subobjects filled.
Marhsall that object to a XML file.
Check that the XML is in the expected format. If not, tweak the mapping.
Following this approach you can be sure that a XML file in the desired format can be unmarshalled to your Java structure. The code to marshall is
CircuitImpactConfigs que = ...
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(que, System.out);

JaxB How to marshall the content of a list as individual tags

I'm using JAXB as marshaller.
I have an object that contains a List of Strings and other properties. I would like to marshall the list contents as individual tag.
I have in code List<String> items. If I add to the list the strings "apple","banana","orange" I would like when marshalling that the resulting xml is:
<items>
<apple/>
<banana/>
<orange/>
</items>
Is this feasable?? Or, to obtain that result, how have I to change the object?
(Sorry for formatting, I cannot do better)
My Advice
My advice is don't do it this way. Instead have your XML message be the following (it will make it easier for everyone to process your XML):
<items>
<item>apple</item>
<item>banana</item>
<item>orange</item>
</items>
How You Could Do It
OK, you have decided not to follow my advice :). Here is how you can do it:
Create an XmlAdapter that can convert a String to an instance of org.w3c.dom.Element with its name equal to the String.
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class StringAdapter extends XmlAdapter<Object, String> {
private Document document;
#Override
public String unmarshal(Object v) throws Exception {
Element element = (Element) v;
return element.getTagName();
}
#Override
public Object marshal(String v) throws Exception {
return getDocument().createElement(v);
}
private Document getDocument() throws Exception {
if(null == document) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
document = db.newDocument();
}
return document;
}
}
Annotate your List<String> field/property with #XmlJavaTypeAdapter pointing to your XmlAdapter and the #XmlAnyElement annotation.
import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Items {
private List<String> items = new ArrayList<String>();
#XmlAnyElement
#XmlJavaTypeAdapter(StringAdapter.class)
public List<String> getItems() {
return items;
}
}
To improve performance, make sure your XmlAdapter holds an instance of Document and set the XMLAdapter on the Marshaller to make it stateful to avoid the need to recreate it each time the XmlAdapter is called.
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Items.class);
Items items = new Items();
items.getItems().add("apple");
items.getItems().add("banana");
items.getItems().add("orange");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setAdapter(new StringAdapter());
marshaller.marshal(items, System.out);
}
}

JAXB location in file for unmarshalled objects

I have some objects being unmarshalled from an XML file by JAXB. Is it possible to have JAXB tell me or somehow find out where in the XML file (line and column) each object comes from?
This information is available at some point, because JAXB gives it to me during schema validation errors. But I would like to have it available for validated objects too.
You could do this in JAXB by leveraging an XMLStreamReader and an Unmarshaller.Listener:
Demo
package forum383861;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.Unmarshaller.Listener;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Customer.class);
XMLInputFactory xif = XMLInputFactory.newFactory();
FileInputStream xml = new FileInputStream("src/forum383861/input.xml");
XMLStreamReader xsr = xif.createXMLStreamReader(xml);
Unmarshaller unmarshaller = jc.createUnmarshaller();
LocationListener ll = new LocationListener(xsr);
unmarshaller.setListener(ll);
Customer customer = (Customer) unmarshaller.unmarshal(xsr);
System.out.println(ll.getLocation(customer));
System.out.println(ll.getLocation(customer.getAddress()));
}
private static class LocationListener extends Listener {
private XMLStreamReader xsr;
private Map<Object, Location> locations;
public LocationListener(XMLStreamReader xsr) {
this.xsr = xsr;
this.locations = new HashMap<Object, Location>();
}
#Override
public void beforeUnmarshal(Object target, Object parent) {
locations.put(target, xsr.getLocation());
}
public Location getLocation(Object o) {
return locations.get(o);
}
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<customer>
<address/>
</customer>
Output
[row,col {unknown-source}]: [2,1]
[row,col {unknown-source}]: [3,5]
Customer
package forum383861;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Customer {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address
package forum383861;
public class Address {
}
For More Information
http://blog.bdoughan.com/2011/08/using-unmarshallerlistener-to-capture.html
I'm afraid not. JAXB builds on top of a XML parser, this one will have built up a logical representation of your XML document forgetting the original string representation of your document.
The validation step is done while your string is still read in, so your parser is able to give you an error message telling you the position of the error. JAXB will only bypass that error message. But as soon as the XML is validated and parsed, only the logical representation will exist.

How to add XML processing instructions during JAXB marshal

I would like to add a processing instruction whenever a collection/array property is serialized to get something like
<alice>
<? array bob ?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Is this possible with JAXB? Or at least with some specific JAXB implementation?
You could leverage an XMLStreamWriter and an XmlAdapter to do this:
BobAdapter
Things to note about the XmlAdapter:
It's stateful and references an XMLStreamWriter. We will later leverage JAXB's ability to set a stateful XmlAdapter on a Marshaller.
It converts from a List<String> to a List<String>, we're just using an XmlAdapter here to get an event.
The marshal method is where we call writeProcessingInstruction on the XMLStreamWriter:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.stream.XMLStreamWriter;
public class BobAdapter extends XmlAdapter<List<String>, List<String>> {
private boolean first = true;
private XMLStreamWriter xmlStreamWriter;
public BobAdapter() {
}
public BobAdapter(XMLStreamWriter xmlStreamWriter) {
this();
this.xmlStreamWriter = xmlStreamWriter;
}
#Override
public List<String> marshal(List<String> stringList) throws Exception {
if(first) {
xmlStreamWriter.writeProcessingInstruction("array", "bob");
first = false;
}
return stringList;
}
#Override
public List<String> unmarshal(List<String> stringList) throws Exception {
return stringList;
}
}
Alice
The #XmlJavaTypeAdapter annotation is used to link the XmlAdapter with the field/property:
package forum6931520;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement
public class Alice {
private List<String> bob;
#XmlJavaTypeAdapter(BobAdapter.class)
public List<String> getBob() {
return bob;
}
public void setBob(List<String> bob) {
this.bob = bob;
}
}
Demo
package forum6931520;
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Alice.class);
File xml = new File("src/forum6931520/input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
Alice alice = (Alice) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
XMLOutputFactory xof = XMLOutputFactory.newFactory();
XMLStreamWriter xmlStreamWriter = xof.createXMLStreamWriter(System.out);
marshaller.setAdapter(new BobAdapter(xmlStreamWriter));
marshaller.marshal(alice, xmlStreamWriter);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<alice>
<?array bob?>
<bob>edgar</bob>
<bob>david</bob>
</alice>
Output
<?xml version='1.0' encoding='UTF-8'?><alice><?array bob?><bob>edgar</bob><bob>david</bob></alice>
Note
This example needs to be run with EclipseLink JAXB (MOXy) as the JAXB RI will throw the following exception (I'm the MOXy lead):
Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
java.util.List is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
java.util.List does not have a no-arg default constructor.
this problem is related to the following location:
at java.util.List
at public java.util.List forum6931520.Alice.getBob()
at forum6931520.Alice
For More Information
http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
UPDATE
I have entered an enhancement request (https://bugs.eclipse.org/354286) to add native support for processing instructions. This would eventually allow you to specify the following on your property:
#XmlProcessingInstruction(target="array", value="bob")
public List<String> getBob() {
return bob;
}

Categories

Resources