JAXB unmarshalling with java generics - java

I am trying to unmarshall an XML document from a legacy system using JAXB. I have an xml structure as follows :
<response>
<id>000000</id>
<results>
<result>
<!-- Request specific xml content -->
<year>2003</year>
<title>Lorem Ipsum</title>
<items>
<item>I1</item>
<item>I2</item>
</items>
</result>
<result>
<year>2007</year>
<title>Dolor sit amet</title>
<items>
<item>K1</item>
<item>K2</item>
</items>
</result>
</results>
</response>
The tags inside the part specified by <result> tag will change depending on my request. Since the content may change I decided to use generics for result items and I have prepared my java beans with annotations as following:
// imports here
#XmlRootElement(name="response")
#XmlAccessorType(XmlAccessType.FIELD)
public class XResponse<T>{
private String id;
#XmlElementWrapper(name="results")
#XmlElement(name="result")
private List<T> results;
// setters and getters
}
...
#XmlRootElement(name="result")
#XmlAccessorType(XmlAccessType.FIELD)
public class X1Result{
private String year;
private String title;
#XmlElementWrapper(name="items")
#XmlElement(name="item")
private List<String> items;
// setters and getters
}
...
I tried unmarshalling the xml document via the code below:
JAXBContext context = JAXBContext.newInstance(XResponse.class, X1Result.class);
Unmarshaller um = context.createUnmarshaller();
XResponse<X1Result> response = (XResponse<X1Result>) um.unmarshal( xmlContent );
List<X1Result> results = unmarshal.getResults();
for (X1Result object : results) {
System.out.println(object.getClass());
}
I have a problem during the unmarshalling that it can't cast the list items into X1Result class. Instead it uses org.apache.xerces.dom.ElementNSImpl.
What should I do to make JAXB Unmarshaller use X1Result class?
Thanks in advance

I think you should use inheritance instead of generics. Given an XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<id>000000</id>
<results>
<result xsi:type="X1Result">
<year>2003</year>
<title>Lorem Ipsum</title>
<items>
<item>I1</item>
<item>I2</item>
</items>
</result>
<result xsi:type="X1Result">
<year>2007</year>
<title>Dolor sit amet</title>
<items>
<item>K1</item>
<item>K2</item>
</items>
</result>
</results>
</response>
You can dynamically bind your <result> entries. You have a top-level type:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "XResult")
#XmlSeeAlso({
X1Result.class
})public abstract class XResult {
}
And you have implementing classes:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "X1Result")
public class X1Result extends XResult {
#XmlElement(name = "year")
private String year;
#XmlElement(name = "title")
private String title;
#XmlElementWrapper(name = "items")
#XmlElement(name = "item")
private List<String> items;
...
}
Use the top-level type in your XResponse class:
#XmlRootElement(name = "response")
#XmlAccessorType(XmlAccessType.FIELD)
public class XResponse {
#XmlElement(name = "id")
private String id;
#XmlElementWrapper(name = "results")
#XmlElement(name = "result")
private List<XResult> results;
...
}
And you can unmarshall using the top-level type:
context = JAXBContext.newInstance(XResponse.class, XResult.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
XResponse response = (XResponse) unmarshaller.unmarshal(new File("testfile.xml"));
List<XResult> results = response.getResults();
for (XResult object : results) {
System.out.println(object.getClass());
}

Related

How can I haver property order in JAXB with nested list and objects

I would like to ask on how to marshal an object with property order from a nested object.
#XmlRootElement(name = "sample")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(propOrder = {"title", "code"})
public class SampleObject {
#XmlAttribute(name = "title")
private String title;
#XmlAttribute(name = "code")
private String code;
}
I have a wrapperList to set that object:
#XmlRootElement(name = "listWrapper")
#XmlAccessorType(XmlAccessType.FIELD)
public class WrapperObject {
#XmlAnyElement(lax=true)
private List<SampleObject> objectList;
}
And i want to set the list on this object. This object is the one who is being marshalled.
#XmlRootElement(name = "marshaller")
#XmlAccessorType(XmlAccessType.FIELD)
public class MarshallerObject {
#XmlElement(name = "wrapperList")
private WrapperObject objectList;
}
This is the output that i'm aiming for:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<marshaller>
<wrapperList>
<sample title="sampleTitle code"001"/>
</wrapperList>
</marshaller>
</soap:Envelope>
Thanks in advance!

JAXB: how to unmarshal a List of objects of different types inside a wrapper?

I'm stucked at parsing the following xml with JAXB:
<?xml version="1.0" encoding="utf-8"?>
<dashboardreport name="exampleDashboard" version="6.5.6.1013" reportdate="2016-12-16T11:05:19.329+01:00" description="">
<data>
<incidentchartdashlet name="Incident Chart" description="" />
<chartdashlet name="WebRequestTime" showabsolutevalues="false" />
<chartdashlet name="WebServiceTime" showabsolutevalues="false" />
</data>
</dashboardreport>
I used the following java classes to unmarshal the xml:
Dashboardreport.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "dashboardreport")
public class Dashboardreport {
#XmlElementWrapper(name = "data")
#XmlElement(name = "chartdashlet")
protected List<Chartdashlet> chartdashlets;
#XmlElementWrapper(name = "data")
#XmlElement(name = "incidentchartdashlet")
protected List<Incidentchartdashlet> incidentchartdashlets;
#XmlAttribute(name = "name")
protected String name;
}
I just want to unmarshal the xml without using a wrapper class around incidentchartdashlets and chartdashlet, cause both types differ a lot.
I only can use the XmlElementWrapper annotation once, so that only chartdashlets get filled and incidentchartdashlets is null.
Is there any solution with JAXB without using a seperate wrapper class?
I assume your dashlet classes are defined like
class Chartdashlet extends Dashlet and class Incidentchartdashlet extends Dashlet.
Then the preferred JAXB way to handle your mixed list of dashlets would be
by using the #XmlElements
annotation:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "dashboardreport")
public class Dashboardreport {
#XmlElementWrapper(name = "data")
#XmlElements({
#XmlElement(name = "chartdashlet", type = Chartdashlet.class),
#XmlElement(name = "incidentchartdashlet", type = Incidentchartdashlet.class)
})
protected List<Dashlet> dashlets;
#XmlAttribute(name = "name")
protected String name;
}

JAXB Generate xmlns:xsi declaration in a nested node

I am looking to control where JAXB generates the xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" declaration when marshalling to XML. I've seen solutions like this to add it to the root element using the JAXB_SCHEMA_LOCATION property, however I don't want it on the root node, I want it somewhere in between. Here's what I've got:
#XmlRootElement(name = "RootNode")
#XmlAccessorType(XmlAccessType.NONE)
public class RootNode {
#XmlElement(name = "IntermediateNode")
private IntermediateNode intermediateNode;
//getter & setter
}
#XmlAccessorType(XmlAccessType.NONE)
public class IntermediateNode {
#XmlElement(name = "MyEntity")
private MyEntity myEntity;
//getter and setter
}
#XmlAccessorType(XmlAccessType.NONE)
public class MyEntity {
#XmlElement(name = "Name")
private String name;
#XmlElement(name = "Title", nillable = true)
private String title;
//getters and setters
}
Serialize like:
MyEntity myEntity = new MyEntity();
myEntity.setName("George");
myEntity.setTitle(null);
IntermediateNode intNode = new IntermediateNode();
intNode.setMyEntity(myEntity);
RootNode rootNode = new RootNode();
rootNode.setIntermediateNode(intNode);
JAXBContext context = JAXBContext.newInstance(RootNode.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(rootNode, System.out);
Produces XML like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RootNode>
<IntermediateNode>
<MyEntity>
<Name>George</Name>
<Title xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</MyEntity>
</IntermediateNode>
</RootNode>
But what I want is:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<RootNode>
<IntermediateNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MyEntity>
<Name>George</Name>
<Title xsi:nil="true"/>
</MyEntity>
</IntermediateNode>
</RootNode>
I even tried moving my IntermediateNode and MyEntity classes into their own package with a package-info.java like this, but that just rolled the xmlns:xsi up to the root element.
#javax.xml.bind.annotation.XmlSchema(
xmlns = {
#javax.xml.bind.annotation.XmlNs(prefix = "xsi", namespaceURI = "http://www.w3.org/2001/XMLSchema-instance")
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.example.intermediate;
Is it possible to get what I want?

Parsing optional nested XML Elements via JAXB

I have XML input that could basically look like this:
<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<object label="" Property1="43" id="2">
<mxCell style="whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="196" y="161" width="200" height="30" as="geometry"/>
</mxCell>
</object>
<object label="" Property2="Helloooo" id="3">
<mxCell style="whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="192" y="269" width="200" height="30" as="geometry"/>
</mxCell>
</object>
<object label="" Property3="23" id="4">
<mxCell style="whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="210" y="383" width="200" height="30" as="geometry"/>
</mxCell>
</object>
</root>
</mxGraphModel>
I'm trying to parse this input via JAXB:
MxGraphModel.java:
#XmlRootElement(name = "mxGraphModel")
#XmlAccessorType(XmlAccessType.FIELD)
public class MxGraphModel {
#XmlElementWrapper(name = "root")
#XmlElement(name = "object")
private ArrayList<MxObject> mxObjects;
#XmlElement(name = "mxCell")
private ArrayList<MxCell> mxCells;
//getters and setters...
}
MxObject.java:
#XmlAccessorType(XmlAccessType.FIELD)
public class MxObject {
#XmlAttribute
private String id;
#XmlAttribute
private String label;
#XmlAttribute
private String documentName;
#XmlAttribute
private String documentDescription;
#XmlElement
private MxCell mxCell;
//getters and setters...
}
MxCell.java:
#XmlAccessorType(XmlAccessType.FIELD)
public class MxCell {
#XmlAttribute
private String id;
#XmlAttribute
private String parent;
#XmlAttribute
private String value;
#XmlAttribute
private String style;
#XmlAttribute
private String vertex;
#XmlElement(name = "mxGeometry")
private MxGeometry geometry;
//getters and setters...
}
DemoController.java:
#RestController
#RequestMapping("/xml")
public class DemoController {
#RequestMapping(method = RequestMethod.POST, consumes = {MediaType.APPLICATION_XML_VALUE})
public void parseXML(#RequestBody MxGraphModel mxGraphModel) {
//mxGraphModel contains EITHER objects of type MxObject
//OR
//MxCell
}
}
My problem is, that the parsing operation results in an MxGraphModel object that contains either a collection of objects OR a collection of mxCells - but never both.
It seems to me that the order in which I declare the XmlElements ("object" and "mxCell") is relevant for the parsing result. When I declare mxCell before object, 2 mxCells are being parsed (which is correct), but the object collection remains null. And the other way around.
Is it possible to have both XmlElements parsed? I don't know whether the issue is related to the fact that an object element could contain an mxCell element itself...
Thanks.
#Dimpre Jean-Sébastien:
You're totally right, sorry for posting the answer this late.
The solution is not to use the root ElementWrapper
So instead of using
#XmlElementWrapper(name = "root")
I had to create a root XmlElement in MxGraphModel.java:
#XmlElement(name = "root")
private MxRoot mxRoot;
Furthermore I created a file called MxRoot.java and moved all collections inside it:
MxRoot.java:
#XmlAccessorType(XmlAccessType.FIELD)
public class MxRoot {
#XmlElement(name = "UserObject")
private ArrayList<MxObject> mxUserObjects;
#XmlElement(name = "object")
private ArrayList<MxObject> mxObjects;
//XmlElement sets the name of the entities
#XmlElement(name = "mxCell")
private ArrayList<MxCell> mxCells;
//getters and setters...
}
It seems the #XmlWrapper Annotation expects exactly one collection. It is not meant to wrap around multiple collections (makes sense now that I know it :))
Thanks to all!

jaxb reference xmlID between xml files

I tried the approach in this post
However I am getting a
>
1 counts of IllegalAnnotationExceptions
XmlIDREF property is referencing a type "java.lang.String" that doesn't have an XmlID property.
this problem is related to the following location:
at private externalReferences.Department
externalReferences.Employee.department
at externalReferences.Employee
at private java.util.List externalReferences.Company.employees
at externalReferences.Company
The two xml Files are the following:
employee.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<company>
<employeeList>
<employee name="Jane Doe" id="1">
<department>1</department>
</employee>
<employee name="John Smith" id="2">
<department>2</department>
</employee>
<employee name="Anne Jones" id="3">
<department>3</department>
</employee>
</employeeList>
</company>
department.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<departmentList>
<departmentList>
<department name="Dev" id="1"/>
<department name="Sales" id="2"/>
<department name="Research" id="3"/>
</departmentList>
</departmentList>
The employee.xml references the department and I want to point to the right department objects when unmarshalling employee.xml.
Classes are as follows:
Company.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Company {
#XmlElementWrapper(name = "employeeList")
#XmlElement(name="employee")
private List<Employee> employees;
#XmlElementWrapper(name = "departmentList")
#XmlElement(name="department")
private List<Department> departments;
public Company() {
employees = new ArrayList<Employee>();
departments = new ArrayList<Department>();
}
...
}
Employee.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
#XmlAttribute
#XmlID
private String id;
public String getId() {
return id;
}
#XmlIDREF
private Employee manager;
#XmlJavaTypeAdapter(EmpAdapter.class)
#XmlIDREF
private Department department;
}
Department.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Department {
#XmlAttribute
#XmlID
private String id;
...
}
DepartmentList.java
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class DepartmentList {
#XmlElementWrapper(name = "departmentList")
#XmlElement(name="department")
private List<Department> departments;
Then I run the following in Main
JAXBContext jc = JAXBContext.newInstance(DepartmentList.class); Unmarshaller unmarshaller = jc.createUnmarshaller();
DepartmentList depList = (DepartmentList) unmarshaller.unmarshal(new FileReader(DepRef));
EmpAdapter adapter = new EmpAdapter();
for(Department dep : depList.getDepartments()) {
adapter.getDepList().put(dep.getId(), dep);
}
JAXBContext jc2 = JAXBContext.newInstance(Company.class);
Unmarshaller unmarshaller2 = jc2.createUnmarshaller();
unmarshaller2.setAdapter(adapter);
Company company2 = (Company) unmarshaller2.unmarshal(new FileReader(empRef));
I feel that having one XMLIDREF refer to employee id and the other XMLIDREF refer to department id is part of the problem. But that is required since the manager field references other employee objects.
Can someone please help me with this. Thank you
The problem arises from class Company that corresponds to an XML document containing employees and departments. However, you've got two separate documents. Apparently you want one final class containing both lists.
(1) You could define a class EmployeeList for employees only, similar to the one for departments (DepartmentList). This will still let you write an application class Company into which you set the references for both lists.
(2) Change the annotation for Company.departments
#XmlTransient
private List<Department> departments;
marshal like you do now, and set the List with the reference you have from unmarshalling the corresponding XML into the returned object.

Categories

Resources