jackson XML serialization: list of inherited classes - java

I want to serialize/deserialize such XML:
<Multi>
<child-001>
<name/>
<value/>
</child-001>
<child-002>
<name/>
<value/>
</child-002>
</Multi>
where the child-001 and child-002 are classes inherited from the same parent.
public abstract class Parent {
private String name;
}
#JacksonXmlRootElement(localName = "child-001")
public class Child001 extends Parent {
private String value;
}
#JacksonXmlRootElement(localName = "child-002")
public class Child002 extends Parent {
private String value;
}
The encapsulating class looks like this:
class Multi {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Parent> nodes = new ArrayList<>();
}
Without #JacksonXmlElementWrapper(useWrapping = false) I've got:
<Multi>
<nodes>
<nodes>
<name>name001</name>
<value>value001</value>
</nodes>
<nodes>
<name>name002</name>
<value>value002</value>
</nodes>
</nodes>
</Multi>
With the annotation I've got:
<Multi>
<nodes>
<name>name001</name>
<value>value001</value>
</nodes>
<nodes>
<name>name002</name>
<value>value002</value>
</nodes>
</Multi>
This is quite close to what I need, but still need to replace "nodes" with "child-001" and "child-002".
Can someone point me where to find a solution? Or should I use JAXB instead of Jackson?
Thanks

Normally to represent exaclty the example XML file, Multi class should look like this and this works well:
class Multi {
#JacksonXmlProperty(localName = "child-001")
private Child001 child001;
#JacksonXmlProperty(localName = "child-002")
private Child002 child002;
}
In your code you are using a List of objects of parent class instead, presumably because you may have any number of elements deserialized into subclasses of Parent ? That's a case of polymorphic deserialization that requires defining type info e.g.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
#JsonSubTypes({
#JsonSubTypes.Type(name = "child-001", value = Child001.class),
#JsonSubTypes.Type(name = "child-002", value = Child002.class)
})
abstract class Parent {
private String name;
}
Unfortunately, due to an as yet (jackson 2.9.2) unresolved issue Jackson expects a wrapper around each element e.g.
<Multi>
<nodes>
<child-001>
<name>n1</name>
<value>v1</value>
</child-001>
</nodes>
<nodes>
<child-002>
<name>n2</name>
<value>v2</value>
</child-002>
</nodes>
</Multi>
Until this issue is resolved consider if the first option. If there are fields you don't want serialized leave them null.

Related

How to write multiple XML files for child beans in Spring Batch ItemWriter?

I have a batch job that retrieves records from a database, processes them, and passes the result into the writer. The bean passed to the writer has four fields that need to be written to separate xml files. One of the fields is a bean representing the original record, and the other three fields are collections of child elements associated with the record.
I initially tried to use jackson to parse the beans and generate the files, but I found that approach had trouble when applied to the batch model.
Next, I've shifted to using StaxEventItemWriters for each child field, which individually seem perfectly adequate, but I'm having trouble implementing a writer that can handle all the various sub-types. I've looked into the CompositeItemWriter and ClassifierCompositeItemWriter, but they seem more suited to having multiple writers for the same type of bean, whereas I need multiple writers appropriate for differing types. Any advice would be greatly appreciated!
Domain example:
public class MyBean {
private RecordBean recordBean;
private List<ChildTypeA> aBeans;
private List<ChildTypeB> bBeans;
private List<ChildTypeC> cBeans;
}
#XmlRootElement(name = "RECORD")
public class RecordBean extends MyAbstractBean {
#XmlElement(name = "ID")
private String recordId;
#XmlElementWrapper(name = "A_CHILDREN")
#XmlElement(name="CHILD_TYPE_A")
List<Long> aChildIds}
#XmlElementWrapper(name = "B_CHILDREN")
#XmlElement(name="CHILD_TYPE_B")
List<Long> bChildIds}
#XmlElementWrapper(name = "C_CHILDREN")
#XmlElement(name="CHILD_TYPE_C")
List<Long> cChildIds}
}
#XmlRootElement(name = "CHILD_TYPE_A")
public class ChildTypeA extends MyAbstractBean {
#XmlElement(name = "ID") private String aId;
}
#XmlRootElement(name = "CHILD_TYPE_B")
public class ChildTypeB extends MyAbstractBean {
#XmlElement(name = "ID") private String bId;
}
#XmlRootElement(name = "CHILD_TYPE_C")
public class ChildTypeC extends MyAbstractBean {
#XmlElement(name = "ID") private String cId;
}
For each container bean passed to the writer, I need to create a unique XML file for each RecordBean e.g. record_1.xml, and I need to write each collection into an aggregate file that will serve as a library of all children for that child type, across all the records.
Output example:
record_1.xml
<?xml version="1.0" encoding="UTF-8"?>
<RECORD>
<ID>1</ID>
<A_CHILDREN>
<CHILD_TYPE_A>1</CHILD_TYPE_A>
<CHILD_TYPE_A>2</CHILD_TYPE_A>
</A_CHILDREN>
<B_CHILDREN>
<CHILD_TYPE_B>1</CHILD_TYPE_B>
<CHILD_TYPE_B>2</CHILD_TYPE_B>
</B_CHILDREN>
<A_CHILDREN>
<CHILD_TYPE_C>1</CHILD_TYPE_C>
<CHILD_TYPE_C>2</CHILD_TYPE_C>
</A_CHILDREN>
</RECORD>
</xml>
record_2.xml
<?xml version="1.0" encoding="UTF-8"?>
<RECORD>
<ID>2</ID>
<A_CHILDREN>
<CHILD_TYPE_A>3</CHILD_TYPE_A>
<CHILD_TYPE_A>4</CHILD_TYPE_A>
</A_CHILDREN>
<B_CHILDREN>
<CHILD_TYPE_B>3</CHILD_TYPE_B>
<CHILD_TYPE_B>4</CHILD_TYPE_B>
</B_CHILDREN>
<A_CHILDREN>
<CHILD_TYPE_C>3</CHILD_TYPE_C>
<CHILD_TYPE_C>4</CHILD_TYPE_C>
</A_CHILDREN>
</RECORD>
</xml>
a_children.xml
<?xml version="1.0" encoding="UTF-8"?>
<A_CHILDREN>
<CHILD_TYPE_A>
<ID>1</ID>
</CHILD_TYPE_A>
<CHILD_TYPE_A>
<ID>2</ID>
</CHILD_TYPE_A>
<CHILD_TYPE_A>
<ID>3</ID>
</CHILD_TYPE_A>
<CHILD_TYPE_A>
<ID>4</ID>
</CHILD_TYPE_A>
</A_CHILDREN>
</xml>
<!-- Decide which format to use -->
b_children.xml & c_children.xml are the same as a_children.xml.
So after the better part of two days, I decided to take a different approach. I think it would be possible to create a set of writers to handle the task, but I think it would require a fair amount of customization, and a complex hierarchy of delegation.
For example:
CompositeItemWriter delegates to:
ClassifierCompositeItemWriter classifies, and delegates to:
StaxEventItemWriter for ChildTypeA
StaxEventItemWriter for ChildTypeB
StaxEventItemWriter for ChildTypeC
MultiResourceItemWriter Dynamically assigns FilesystemResource for each RecordBean.
Despite the fact that it probably could be done, I realized that it's just simpler to write the XML as strings wherever I want. Something along the lines of:
#Component
#Scope("step")
public class MyXmlWriter implements ItemWriter<MyBean> {
#Override
public void write(List<? extends MyBean> beans) throws Exception {
for(MyBean bean : beans) {
this.writeRecordBean(bean.getRecordBean());
bean.getAChildIds().forEach(a -> this.writeElement(a, pathA));
bean.getBChildIds().forEach(b -> this.writeElement(b, pathB));
bean.getCChildIds().forEach(c -> this.writeElement(c, pathC));
}
}
private void writeElement(Long Id, Path path) {
// Handle serialization and FileIO here
}
.
.
.
}
Anyway, I hope this helps anyone digging into the Spring Batch weeds like I was. Cheers!

jackson fasterxml add namespaces to root element

If I have the class A.java:
#JacksonXmlRootElement(localName = "A")
public class A {
}
The output that gets produced is:
<A
xmlns="">
I want to add a few more namespaces to the output, i.e.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.com example.xsd"
How do I configure A.java to contain more custom namespaces like these?
Since xsi:schemaLocation is an attribute, you can add it like that:
public class A implements Serializable {
#JacksonXmlProperty(isAttribute = true, localName = "xsi:schemaLocation")
private String schemaLocation = "urn:path:to.your.schema";
It did the job for me.

Problems with JAXB marshalling lists of non-annotated objects

I've got a bit of an issue with unmarshalling a list of closed objects using JAXB (closed in the sense that I cannot add JAXB annotation) . Basically, my XML looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<document>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<!-- SNIP! -->
</rdf:RDF>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<!-- SNIP! -->
</rdf:RDF>
</document>
And my Document class is:
#XmlRootElement(name = "document", namespace = Namespace.DEFAULT_NAMESPACE)
#XmlAccessorType(XmlAccessType.FIELD)
public class Document {
#XmlJavaTypeAdapter(ModelAdapter.class)
#XmlElement(name = "RDF", namespace = Namespace.RDF_NAMESPACE)
private List<Model> models;
....
Where Model is a class from a framework that I cannot add JAXB annotations to, hence the adaptor.
The implementation of ModelAdapter is as follows:
public class ModelUnmarshalAdapter extends ModelAdapter<Object, Model> {
#Override
public Model unmarshal(final Object v) throws Exception {
// Turn incoming Node into a Model object
Model model = convert(v);
return model;
}
....
}
When I unmarshal the XML I'm finding ModelUnmarshalAdapter.unmarshal() is being called twice as expected (due to the 2 RDF elements in the XML), but the Document instance models property is always null. It's like it doesn't instantiate the necessary list instance.
Any ideas would be greatly appreceiated.
Thanks
Nick
After much trial and error it turns out the solution was to subclass the closed object concrete class (in my case ModelCom, which implements the Model interface) and add the #XmlJavaTypeAdapter to that
#XmlJavaTypeAdapter(ModelAdapter.class)
public class MyModel extends ModelCom {
public Model(Graph base) {
super(base);
}
public Model(Graph base, Personality<RDFNode> personality) {
super(base, personality);
}
}
The Document class is now simply
public class Document {
#XmlElement(name = "RDF", namespace = Namespace.RDF_NAMESPACE)
private List<MyModel> models;

JAXB Unmarshal a list of objects

I have the following XML that I am trying to unmarshal:
<ListOfYear>
<Requests/>
<Payload>
<Year>
<Value>2013</Value>
</Year>
<Year>
<Value>2012</Value>
</Year>
<Year>
<Value>2011</Value>
</Year>
<Year>
<Value>2010</Value>
</Year>
<Year>
<Value>2009</Value>
</Year>
<Year>
<Value>2008</Value>
</Year>
</Payload>
</ListOfYear>
My java class looks like this:
#XmlRootElement(name = "ListOfYear")
public class YearOutput {
#XmlElementWrapper(name = "Payload")
#XmlElement(name = "Year")
private ArrayList<Year> Payload;
public ArrayList<Year> getPayload() {
return Payload;
}
public void setPayload(ArrayList<Year> payload) {
Payload = payload;
}
}
Payload should contain a list of year objects:
#XmlRootElement(name = "Year")
#XmlAccessorType(XmlAccessType.FIELD).
public class Year {
#XmlElement(name = "Value")
int Value;
public int getValue() {
return Value;
}
public void setValue(int value) {
Value = value;
}
}
I am unmarshaling the XML with the following code:
String r = response.getEntity(String.class);
JAXBContext jaxbContext = JAXBContext.newInstance(YearOutput.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
YearOutput output = (YearOutput) unmarshaller.unmarshal(new StringReader(r));
I get an output object back just fine, the payload object is always null. I have tried several different approaches with my XML annotation and have not been able to get anything to work. Any help would be much appreciated.
EDIT
I thought the name space was inconsequential so I didn't post that part of the code. But #Blaise Doughan suggested I marshal my model and it appears my namespace may be causing an issue...
Here is the full XML that I need:
<ListOfYear xmlns="http://something.com/">
<Requests/>
<Payload>
<Year>
<Value>2013</Value>
</Year>
<Year>
<Value>2012</Value>
</Year>
<Year>
<Value>2011</Value>
</Year>
<Year>
<Value>2010</Value>
</Year>
<Year>
<Value>2009</Value>
</Year>
<Year>
<Value>2008</Value>
</Year>
</Payload>
</ListOfYear>
Here is my full model:
#XmlRootElement(name = "ListOfYear", namespace = "http://something.com/")
public class YearOutput {
#XmlElementWrapper(name = "Payload")
#XmlElement(name = "Year")
private ArrayList<Year> Payload;
public ArrayList<Year> getPayload() {
return Payload;
}
public void setPayload(ArrayList<Year> payload) {
Payload = payload;
}
}
Now when I marshal my model I am getting:
<?xml version="1.0" encoding="UTF-8"?>
<ns2:ListOfYear xmlns:ns2="https://something.com/">
<Payload>
<Year>
<Value>2008</Value>
</Year>
</Payload>
</ns2:ListOfYear>
So what am I doing wrong with my namespace?
EDIT
After adding package-info.java everything works perfect!
#XmlSchema(
namespace = "https://something.com/",
elementFormDefault = XmlNsForm.QUALIFIED)
package example;
import javax.xml.bind.annotation.XmlNsForm;
import javax.xml.bind.annotation.XmlSchema;
Your annotations need to go on the property (get or set method) instead of the field. If you wish to have them on the field (instance variable), the you need to annotate your class with #XmlAccessorType(XmlAccessType.FIELD), like you have on your Year class.
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
UPDATE
Instead of specifying the namespace on #XmlRootElement you will need to leverage a package level #XmlSchema annotation (on a class called package-info) to match the namespace qualification.
http://blog.bdoughan.com/2010/08/jaxb-namespaces.html
Try adding a / to the ending Payload tag.
A it's already mentioned, the XML file should contain
</Payload>
instead of
<Payload>
in the last but one line
#XmlElement should be above attribute but not getter in Year class:
#XmlElement int Value;
The element name in XmlElement annotation on getValue is missing. JAXB default behavior is that getValue means 'value' element, not 'Value'. Add the annotation #XmlElement(name="Value")
Fix it as in your other code
#XmlRootElement(name = "Year")
public static Year {
...
#XmlElement(name="Value")
public int getValue() {
return Value;
}
...
}
If you also marshal I recommend to add #XmlAccessorType(XmlAccessType.NONE) to the classes to prevent adding both 'value' and 'Value' elements.
See full working code at package 'wicketstuff/enteam/wicket/examples14/mixed' on
https://repo.twinstone.org/projects/WISTF/repos/wicket-examples-1.4/browse/
Also a test that log into console your XML and parse it back is provided in 'YearOutputTest'

Dynamic java from XML

I would like to build some kind of object generation engine for my domain objects.
For example, lets assume, I'm working with graphs. The models are represented by xml and I should be able to load them and build a java representation at runtime.
Lets say, graph has vertices and edges
So it will look like this:
<graph>
<vertex id="n1" color="red", thickness="2">
<vertex id="n2">
<edge end1="${n1}", end2="${n2}"/>
</graph>
For this I would like to get the objects that can be described by the following java classes:
class Graph {
List<Vertex> vertexList
List<Edge> edgeList
}
class Vertex {
String id
... various properties ...
}
class Edge {
Vertex end1
Vertex end2
}
Another requirement is to be able to define vertices in loop like this:
<graph>
...
<for var = i, min = 1, max = 10, step = 1>
<vertex id=$i.../>
</for>
...
</graph>
I thought about using Apache Jelly but it seems to be a 'dead' project, JaxB doesn't allow such a level of dynamic behavior AFAIK...
My question is - what framework can you recommend for implementing such a task?
If there is something that works like Apache Jelly but still maintained, it could be great also :)
Thanks a lot in advance
JAXB (JSR-222) implementations can easily handle references within a document using a combination of #XmlID and #XmlIDREF. I will demonstrate below with an example.
JAVA MODEL
Graph
package forum13404583;
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
class Graph {
#XmlElement(name = "vertex")
List<Vertex> vertexList;
#XmlElement(name = "edge")
List<Edge> edgeList;
}
Vertex
In the Vertex class you need to use the #XmlID annotation to indicate that the id field is the id.
package forum13404583;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
class Vertex {
#XmlAttribute
#XmlID
String id;
#XmlAttribute
String color;
#XmlAttribute
Integer thickness;
}
Edge
In the Edge class the #XmlIDREF annotation is used to indicate that the XML value contains a foreign key that references the real value.
package forum13404583;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
class Edge {
#XmlAttribute
#XmlIDREF
Vertex end1;
#XmlAttribute
#XmlIDREF
Vertex end2;
}
DEMO CODE
package forum13404583;
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Graph.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum13404583/input.xml");
Graph graph = (Graph) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(graph, System.out);
}
}
INPUT (input.xml)/OUTPUT
Below is the input to and output from running the demo code.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<graph>
<vertex id="n1" color="red" thickness="2"/>
<vertex id="n2"/>
<edge end1="n1" end2="n2"/>
</graph>
For More Information
http://blog.bdoughan.com/2010/10/jaxb-and-shared-references-xmlid-and.html

Categories