Jackson XML unmarshalling and abstract classes - java

I'm trying to unmarshal the following XML using Jackson 2.4.0 XmlMapper:
<root>
<a/>
<b/>
</root>
...and the following POJO
class Root {
#JacksonXmlElementWrapper(useWrapping = false)
#JsonSubTypes({
#JsonSubTypes.Type(name = "a", value = POJO_A.class),
#JsonSubTypes.Type(name = "b", value = POJO_B.class)
})
public final List<AbstractPOJO> objects = new ArrayList<>();
}
I've also tried with JAXB #XmlElements annotations with the same result, which is:
Unrecognized field "a" (class Root), not marked as ignorable (1 known properties: "objects"])
So it seems that Jackson thinks my list is called "objects" instead of "a" and "b". Normally I fix that by using #JsonProperty("newName") but in this case I expected that to be handled by the #JsonSubtypes or #XmlElements annotations.
As I cannot modify the input XML, is there anything else I can do, either with Jackson, Jackson XML or JAXB annotations?
Update: Forgot to say, the problem is deserializing to the same collection (because I need to keep the order and they can be mixed). Doing it in separate fields works just fine.

When I was trying peeskillet answer and changed Jackson for JAXB unmarshaller, using #XmlElements/#XmlElementRefs worked (#XmlAnyElement did not for some reason, I was getting a list of ElementNSImpl instead of my own classes).
It will be nice to have this working with Jackson, but in the meantime, this is the way to make this work.
UPDATE:
What I did was:
class Root {
#XmlElements({
#XmlElement(name = "a", type = PojoA.class),
#XmlElement(name = "b", type = PojoB.class)
})
public final List<AbstractPOJO> objects = new ArrayList<>();
}
I also added a #XmlRootElement(name = "a") to PojoA, but I think it's not used when you have #XmlElements

"As I cannot modify the input XML, is there anything else I can do, either with Jackson, Jackson XML or JAXB annotations?"
I'm not too familiar with Jackson's Xml features. But with the JAXB Unmarshaller and a little change to your annotations, this can be achieved.
For your List<AbstractPojo> you can use the #XmlAnyElement(lax = true).
With lax = true:
If true, when an element matches a property marked with XmlAnyElement is known to JAXBContext (for example, there's a class with XmlRootElement that has the same tag name, or there's XmlElementDecl that has the same tag name), the unmarshaller will eagerly unmarshal this element to the JAXB object, instead of unmarshalling it to DOM
Which basically means that if we annotate PojoA and PojoB with #XmlRootElement and pass the element name as the name attribute (#XmlRootElement(name = "a")), by definition, this should work.
Let's give it a shot:
public abstract class AbstractPojo {
// note this class is not annotated. It will be known in the context
// as we're explicitly using the type int the Root class
}
#XmlRootElement(name = "a")
public class PojoA extends AbstractPojo {
}
#XmlRootElement(name = "b")
public class PojoB extends AbstractPojo {
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "root")
public class Root {
#XmlAnyElement(lax = true)
protected List<AbstractPojo> objects;
public List<AbstractPojo> getObjects() {
if (objects == null) {
objects = new ArrayList<>();
}
return this.objects;
}
}
Using the following xml file to test
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a/> <b/> <b/> <a/> <a/> <b/> <b/>
</root>
And the following test program
public class JaxbTest {
private static final String FILE_NAME = "test.xml";
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(Root.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
File f = new File(FILE_NAME);
Root root = (Root)unmarshaller.unmarshal(f);
List<AbstractPojo> list = root.getObjects();
for (AbstractPojo p : list) {
System.out.print( p instanceof PojoA ? "a " : "b ");
}
System.out.println();
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(root, System.out);
}
}
We get the result we're looking for.
a b b a a b b // along with the xml file content we marshal for testing
Here are some good resources:
JAXB tutorial
Oracle JAXB tutorial
#XmlAnyElement
Blaise Doughan's blog is always a good resource
UPDATE
Ok I see why you were getting ElementNSImpl. This doesn't work for me, I'm getting ElementNSImpl objects. Do you know what's happening?
Yea I see what's happening. Firstly, I compiled an xsd with xjc, and it created an ObjectFactory for me, which declared the elements. That's why it way working for me.
If you don't do this, then you should explicitly put the PojoA and PojoB into the context.
JAXBContext.newInstance(Root.class, PojoA.class, PojoB.class);

Related

JAXB Deserialization to object return null

I need to transform the XML below to a POJO. I cannot modify the XML. My problem is the upsert object is always null.
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<MarginCall xmlns="http://my-name-space" type="Upsert">
<upsert>
<AccountId>ABCD</AccountId>
</upsert>
</MarginCall>
MarginCall Class
#XmlRootElement(name="MarginCall", namespace="http://my-name-space")
#XmlAccessorType(XmlAccessType.FIELD)
public class MarginCall {
#XmlAttribute
private String type;
#XmlElement
protected Upsert upsert;
// Getters, setters and ToString()
Upsert Class
#XmlType(name = "upsert", namespace="")
#XmlAccessorType(XmlAccessType.FIELD)
public class Upsert {
#XmlElement(name="AccountId")
private String accountId;
// Getter, setter and ToString()
How I transform the XML (received as String:message below)
JAXBContext jaxbContext;
jaxbContext = JAXBContext.newInstance(new Class[]{com.sandrew.MarginCall.class});
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
MarginCall marginCall = (MarginCall) jaxbUnmarshaller.unmarshal(new StringReader(message));
System.out.println(marginCall); // prints: MarginCall{type='Upsert', upsert=null} <---- PROBLEM
I tried to change, remove the namespace on Upsert, without luck. I have also created a MarginCall object, then tried to write it as an XML, and confirmed the result is structured as I want:
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
Upsert upsert = new Upsert();
upsert.setAccountId("ABCD");
MarginCall m = new MarginCall();
m.setType("T");
m.setUpsert(upsert);
jaxbMarshaller.marshal(m, new File("res.xml"));
// res.xml = <?xml version="1.0" encoding="UTF-8" standalone="yes"?><ns2:MarginCall xmlns:ns2="http://my-name-space" type="T"><upsert><AccountId>ABCD</AccountId></upsert></ns2:MarginCall>
Any idea why when I go from String to POJO, I get null on the upsert object ?
In XML the namespace definition xmlns="http://my-name-space"
in the root element implicitly inherits to the nested elements.
However, in Java this implicit inheriting is not the case.
You need to code this explicitly.
Therefore you need to specify this namespace also for the properties
representing the non-root elements.
You do this by specifying the namespace in their
#XmlElement annotations.
In class MarginCall
#XmlElement(namespace="http://my-name-space")
protected Upsert upsert;
In class Upsert
#XmlElement(name="AccountId", namespace="http://my-name-space")
private String accountId;
After having done this, the output of the unmarshalled object will be
MarginCall(type=Upsert, upsert=Upsert(accountId=ABCD))

Generic JAXB to different XMLs wrapped by same XML

I have a XML in which some common part that wrappes a specific part that can change in any way.
For example I would have to manage these 2 XML (simplified):
...
<xml>
<common>
<data1>1</data1>
<data2>2</data2>
</common>
<specific>
<specific-info>
<color>blue</color>
</specific-info>
</specific>
</xml>
...
And this one:
...
<xml>
<common>
<data1>33</data1>
<data2>42</data2>
</common>
<specific>
<another-info>
<age>42</age>
</another-info>
</specific>
</xml>
...
So I've inherit this code (simplified), using JAXB, that works:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {})
#XmlRootElement(name = "xml")
public class Specific{
#XmlElement(name = "common", required = true)
protected CommonData common;
#XmlElement(name = "specific")
protected SpecificInfo specificInfo;
#XmlElement(name = "specific")
protected AnotherInfo anotherInfo;
// getters and setters
}
The problem is that when a new sort of info arrives I've to add new XMLElement with same name and I think that it smells... And also that they are getters an setters for each of it.
There's another way to afford this? It's a standar way to unwrap a wrapped XML with JAXB?
In the way that #fisc showed me I used in my bean #XmlAnyElement in his way:
#XmlAnyElement(lax=true)
public Object[] others;
Which gets the specific part of the xml as an xml DOM object and ALSO this method to get the actual object and not a DOM representation of whatever there is in the xml:
#SuppressWarnings("unchecked")
public <T> T getOthersParsedAs(Class<T> clazz) throws JAXBException{
JAXBContext context = JAXBContext.newInstance(clazz);
Unmarshaller unmarshaller = context.createUnmarshaller();
T res = (T) unmarshaller.unmarshal((Node)others[0]);
if (res instanceof JAXBElement){
res = (T)JAXBIntrospector.getValue(res);
}
return res;
}
This way I cant get them with:
Specific spec = ...
SpecificInfo info = spec.getOthersParsedAs(SpecificInfo.class);
or:
AnotherInfo info = spec.getOthersParsedAs(AnotherInfo .class);
UPDATE:
I've done a method to insert any object to that xml in that node (ugly but shows all the code in same method):
public <T> void setOthersInXML(T data) throws JAXBException, ParserConfigurationException{
JAXBContext context = JAXBContext.newInstance(data.getClass());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db= dbf.newDocumentBuilder();
Document document = db.newDocument();
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(data, document);
others = new Object[]{document.getDocumentElement()};
}
And is used like a setter.
Edited again
Because I found a problem: if the class haven't well defined the XMLRootElement getOthersParsedAs will return you a JAXBElement object and this could be problematic, so I've added the check to the method
If I understand your need, you want to be able to get content of another elements into <specific/> ?
so maybe you can take a look to #XmlAnyElement depending on the structure your want to catch.
you might be interested in lax attribute aiming to preserve the processing of specificInfo and anotherInfo
#XmlAnyElement(lax="true")
public Object[] others;

JAXB Unmarshal List of Interfaces

I am trying to wrap 2 Classes, that are built following the Composite pattern, with JAXB.
The whole thing i built like this:
public interface ICriterion {String somemethod(String arg);}
#XmlRootElement(name = "criterion")
#XmlAccessorType(XmlAccessType.FIELD)
public class Criterion implements ICriterion {
#XmlElement String name;
#XmlElement String data;
//getter, setter, somemethod(), Criterion(), Criterion(String, String)
}
#XmlRootElement(name = "criteria")
#XmlAccessorType(XmlAccessType.FIELD)
public class Criteria implements ICriterion {
#XmlAnyElement(lax=true)
private final List<ICriterion> criteria;
//getter, setter, somemethod(),
Criteria(){criteria = new ArrayList<>();}
Criteria(List<ICriterion>)...
}
i took this solution from this answer
It works fine for marshalling - i can provide a Criteria, which contain a list of Criteria and Criterions, it gets marshaled to xml correctly.
What does not work however is unmarshaling a string to an Object
String request =
<criteria>
<criteria>
<criterion>
<name>number1</value>
<data>123123</token>
</criterion>
<criterion>
<name>number2</value>
<data>1223323</token>
</criterion>
</criteria>
<criterion>
<name>number3</value>
<data>1212</token>
</criterion>
</criteria>
JAXBContext ctx = JAXBContext.newInstance(Criteria.class, Criterion.class);
StringReader reader = new StringReader(request);
Criteria wrapped = (Criteria) ctx.createUnmarshaller().unmarshal(reader);
gives:
java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
I've tried other ways that are avaiable through online sources like:
#XmlElementRefs({ #XmlElementRef(type=Criterion.class), #XmlElementRef(type=Criteria.class)})
#XmlElements({#XmlElement(type=Criteria.class),#XmlElement(type=Criterion.class)})
which do not give an error, but result in the List being empty instead. The solution to this problem, is using #XmlAnyElement(lax=true), which does not work for me.
My first guess was, that somehow the List is not initialized correctly in my Constructor and JAXB uses some kind of fixed size list.
My countermeasure was to explicitly initialize the list in the no-Args constructor
Criteria(){criteria = new ArrayList<>();}
The problem also is not solved by explicitly making "critera" an ArrayList<> instead of List<>.
What am I missing?

How do I programmatically create XML from Java?

I am trying to programmatically create XML elements using JAXB in Java. Is this possible? I am reading this page here for something I can use, but have so far found nothing.
Usually you start by defining a bean
#XmlRootElement public class MyXML {
private String name;
public String getName() { return name; }
#XmlElement public void setName(String s) { this.name = s; }
}
and serialize it with code like
public class Serializer {
static public void main(String[] args) {
MyXML m = new MyXML();
m.setName("Yo");
JAXBContext jaxbContext = JAXBContext.newInstance(MyXML.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(m, new File("MyXML_"+ ".xml"));
}
}
that whould produce the following XML
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myXML>
<name>Yo</name>
</myXML>
How would I program my Java class to create the element tag name depending on what is entered in the program? For instance in my example the tag element is called 'name'. How could I set this at runtime though? Is this possible with generics or some other way?
The B in JAXB stands for Bean so no, there's no way to use JAXB without defining beans.
You just want to dinamically create an XML so take a look at jOOX for example (link to full Gist)
Document document = JOOX.builder().newDocument();
Element root = document.createElement("contacts");
document.appendChild(root);
for (String name : new String[]{"John", "Jessica", "Peter"}) {
$(root).append(
$("contact"
, $("name", name)
, $("active", "true")
)
);
}
Here, you use annotation before compile-time while you have no knowledge yet of the format you will need.. Marshalling this way is not that different from serializing, and it basically map directly the fields of a java object to an XML representation --> (if something is not defined in the object, it won't appear in the representation).
What you thrive to do looks like simple xml crafting (a XML parser would be enough S(t)AX/DOM whatever -- I like Jackson).
For the sake of curiosity, if you really want to fiddle with annotation you can use a bit of reflection in conjonction with the answer you will find here

#XmlElement with multiple names

I have a situation here, trying to act as a gateway between two APIs. What I need to do, is:
make a request to an APIa;
parse (marshal) the XML response into an java object;
make little changes to it;
and then give a response in XML (unmarshal) to the other end (APIb).
The thing is that I use the same object to parse the API response and to send the response to the other end.
public class ResponseAPI{
#XmlElement(name="ResponseCode") //I receive <ResponseCode> but I need to send <ResultCode>
private String responseCode;
//getter and setter
}
as the comment says: I receive but I need to send
Is there a way to get this done without having to create another extra class which carries ResultCode?
thanks in advance!
You can try next solution using #XmlElements annotaion
#XmlAccessorType(XmlAccessType.FIELD)
public class ResponseAPI
{
#XmlElements(
{
#XmlElement(name = "ResponseCode"),
#XmlElement(name = "ResultCode")
})
private String responseCode;
// ...
}
In this case both ResponseCode and ResultCode will be used during unmarshalling (xml -> object) and only ResultCode during marshalling (object -> xml).
So you can unmarshall XML like
<responseAPI>
<ResponseCode>404</ResponseCode>
</responseAPI>
After marshalling object will looks like
<responseAPI>
<ResultCode>404</ResultCode>
</responseAPI>
Note:
The answer given by Ilya works but isn't guaranteed to work across all implementations of JAXB or even across versions of a single JAXB implementation. The #XmlElements annotation is useful when the decision of which element to marshal depends on the type of the value (see: http://blog.bdoughan.com/2010/10/jaxb-and-xsd-choice-xmlelements.html). In your use case both the ResponseCode and ResultCode elements correspond to type String, unmarshalling will always work fine, but the choice of which element to output is arbitrary. Some JAXB Impls may have last specified wins, but others could easily have first wins.
You could do the following by leveraging #XmlElementRef.
Java Model
ResponseAPI
We will change the responseCode property from type String to JAXBElement<String>. The JAXBElement allows us to store the element name as well as the value.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ResponseAPI{
#XmlElementRefs({
#XmlElementRef(name = "ResponseCode"),
#XmlElementRef(name = "ResultCode")
})
private JAXBElement<String> responseCode;
public JAXBElement<String> getResponseCode() {
return responseCode;
}
public void setResponseCode(JAXBElement<String> responseCode) {
this.responseCode = responseCode;
}
}
ObjectFactory
The #XmlElementRef annotations we used on the ResponseAPI class correspond to #XmlElementDecl annotations on a class annotated with #XmlRegistry. Traditionally this class is called ObjectFactory but you can call it anything you want.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name="ResponseCode")
public JAXBElement<String> createResponseCode(String string) {
return new JAXBElement<String>(new QName("ResponseCode"), String.class, string);
}
#XmlElementDecl(name="ResultCode")
public JAXBElement<String> createResultCode(String string) {
return new JAXBElement<String>(new QName("ResultCode"), String.class, string);
}
}
Demo Code
input.xml
<responseAPI>
<ResponseCode>ABC</ResponseCode>
</responseAPI>
Demo
When creating the JAXBContext we need to ensure that we include the class that contains the #XmlElementDecl annotations.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ResponseAPI.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("Scratch/src2/forum24554789/input.xml");
ResponseAPI responseAPI = (ResponseAPI) unmarshaller.unmarshal(xml);
ObjectFactory objectFactory = new ObjectFactory();
String responseCode = responseAPI.getResponseCode().getValue();
JAXBElement<String> resultCodeJAXBElement = objectFactory.createResultCode(responseCode);
responseAPI.setResponseCode(resultCodeJAXBElement);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(responseAPI, System.out);
}
}
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<responseAPI>
<ResultCode>ABC</ResultCode>
</responseAPI>

Categories

Resources