I am trying to unmarshall the middle elements of a big xml document. Currently using JAXB and Woodstox.
Example of xml middle elements that I need to unmarshall:
<Values>
<Person ID="ABC">
<FirstName>Shawn</FirstName>
<LastName>Mark</LastName>
<Age>3</Age>
</Person>
<Person ID="DEF">
<FirstName>John</FirstName>
<LastName>Durell</LastName>
<Age>4</Age>
</Person>
</Values>
The jaxb classes that I use are:
#XmlRootElement(name = "Values")
#XmlAccessorType(XmlAccessType.FIELD)
public class Attributes
{
#XmlElement(name = "Person")
private ArrayList<Person> persons;
public ArrayList<Person> getPersons()
{
return persons;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
public class Person
{
#XmlAttribute
private String ID;
#XmlElement(name = "FirstName")
private String firstName;
#XmlElement(name = "LastName")
private String lastName;
#XmlElement(name = "Age")
private String age;
}
I am able to unmarshall all values except the ID. Its being shown as null.
Here is the code:
final XMLInputFactory xif = XMLInputFactory.newInstance();
final StreamSource xml = new StreamSource(pathToxmlFile);
XMLStreamReader xsr;
xsr = xif.createXMLStreamReader(xml);
xsr.nextTag();
while (!xsr.getLocalName().equals("Values"))
{
xsr.nextTag();
}
final JAXBContext jc = JAXBContext.newInstance(Attributes.class);
final Unmarshaller unmarshaller = jc.createUnmarshaller();
final JAXBElement<Attributes> jb = unmarshaller.unmarshal(xsr, Attributes.class);
The above code is working only when the <Values> is nested 5-6 levels from the root. If there exists 15 tags before <Values>, this code isn't working.
Also its comparatively very slow when compared to just only using JAXB and unmarshalling all elements, but that would require me to create objects for data which will never be used.
So, my questions are -- Is there anyway to increase the performance?
Why wouldn't it work when its nested deep in the xml?
How to get the ID value from Person attribute?
The following should help:
Why wouldn't it work when its nested deep in the xml?
If by not working you mean throwing an exception like:
Exception in thread "main" javax.xml.stream.XMLStreamException: ParseError at [row,col]:[4,13]
Message: found: CHARACTERS, expected START_ELEMENT or END_ELEMENT
at com.sun.org.apache.xerces.internal.impl.XMLStreamReaderImpl.nextTag(XMLStreamReaderImpl.java:1247)
at blog.stax.middle.UnmarshalDemo.main(UnmarshalDemo.java:15)
The you could change the code that advances the XmlStreamReader to:
while(xsr.hasNext()) {
if(xsr.isStartElement() && xsr.getLocalName().equals("Values")) {
break;
}
xsr.next();
}
Is there anyway to increase the performance?
StAX is a very fast way to parse an XML document. It is probably being used by your JAXB implementation anyways. String comparison can be slow.
Since you are using Woodstox and it interns element names (see: section 6.1 String interning: http://woodstox.codehaus.org/FAQ). You could do identity checks on the strings instead of the equals methods.
if(Boolean.TRUE.equals(xsr.getProperty("org.codehaus.stax2.internNames"))) {
while(xsr.hasNext()) {
if(xsr.isStartElement() && xsr.getLocalName() == "return") {
break;
}
xsr.next();
}
} else {
while(xsr.hasNext()) {
if(xsr.isStartElement() && xsr.getLocalName().equals("return")) {
break;
}
xsr.next();
}
}
How to get the ID value from Person attribute?
By default the XML your JAXB (JSR-222) implementation will be map your ID field to an attribute called id and not ID. You can override this default as follows:
#XmlAttribute(name="ID")
private String ID;
Related
I am working with a colleague's API. The API returns a Response with a list of objects or just one, singular object. The objects can be of multiple types. The return type is in XML. I am interested in parsing this XML via JAXB to get my classes, ideally in a flexible and generic way.
The following two XML responses are a sample of what I am speaking about.
Sample 1: A response with a list Jobs containing Job object.
<Response>
<Status>OK</Status>
<Jobs>
<Job>
<ID>J1</ID>
<Name>job name</Name>
</Job>
<Job>
<ID>J2</ID>
<Name>job name</Name>
</Job>
</Jobs>
</Response>
Sample 2: A response with one Job.
<Response>
<Status>OK</Status>
<Job>
<ID>J123</ID>
<Name>job name</Name>
</Job>
</Response>
At the moment, I am constructing something as follows:
#XmlRootElement(name="Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
#XmlElement(name = "Status")
protected Status status;
#XmlAnyElement(lax=true)
protected Object items;
}
Unmarshalling via
Response = (Response) unmarshaller.unmarshal(myXmlResponse);
But I'm receiving null in my items when unmarshalling Sample 1. Also, this approach gives me a bad feeling as I'm using Object as a catch-all i.e. expecting both List<Job> and Job type. What am I doing wrong? Is there a better solution? Maybe my response class can have two generics, one for list of items and another for a single item?
An approach in which the singular <Job> is converted to a list of jobs with one element would also be interesting, but I'm not sure that can be a generic without modifying the XML response.
You could do this:
#XmlRootElement(name = "Response")
public class Response {
#XmlElement(name ="Status")
private Status status;
#XmlElements({
#XmlElement(name = "Job", type = Job.class),
#XmlElement(name = "Jobs", type = Jobs.class),
})
private List<?> jobs;
}
Then Job would be:
public class Job {
#XmlElement(name = "ID")
private String id;
#XmlElement(name = "Name")
private String name;
}
And Jobs:
public class Jobs {
#XmlElement(name = "Job")
private List<Job> jobs;
}
Update to answer on comment:
This is the cleanest way I could think of for handling these described payloads. The challenge is with the <Jobs></Jobs> being there only some times.
There is a way to do it without embedded list but it is messier. I will copy it below so you can decide if you like it, or better to get another cleaner solution.
#XmlRootElement(name = "Response")
public class Response {
#XmlElement(name ="Status")
private Status status;
#XmlElement(name = "Job")
private List<Job> jobs;
#XmlElementWrapper(name = "Jobs")
#XmlElement(name = "Job")
private List<Job> jobsWrapped;
}
I have a String object with xml data in it .
I want that data in POJO , i tried using JAXB unmarshaller to convert but it always give me null values in the object attributes.
this is my code :
ResponseEntity<String> response = restTemplate.getForEntity("https://api.flickr.com/services/rest/?api_key=MY_API_KEY&method=flickr.photos.search&tags=nature", String.class);
String resp = response.getBody();
JAXBContext jaxBcontext = JAXBContext.newInstance(Resp.class);
Unmarshaller unmarshaller = jaxBcontext.createUnmarshaller();
Resp respObj = (Resp)unmarshaller.unmarshal(new StringReader(resp));
the value in String is :
<rsp stat="ok">
<photos page="1" pages="4226" perpage="100" total="422597">
<photo id="28534349567" owner="79805131#N08" secret="b8bd7fe7cb"
server="843" farm="1" title="Savoie S006." ispublic="1" isfriend="0"
isfamily="0"/>
<photo id="43355895332" owner="155237230#N05" secret="75fd48d040"
server="1769" farm="2" title="IMG_3139" ispublic="1" isfriend="0"
isfamily="0"/>
<photo id="41595746070" owner="125407841#N08" secret="1f216ab8b8"
server="1822" farm="2" title="" ispublic="1" isfriend="0" isfamily="0"/>
</photos>
</rsp>
the POJOS are :
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "rsp")
public class Resp {
#XmlElement(name="stat")
private String stat;
#XmlElement(name="photos" , type = Photos.class)
private Photos photos;
public String getStat() {
return stat;
}
//constructors and getter setters
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "photos")
public class Photos {
#XmlElement(name="total")
private String total;
#XmlElement(name="page")
private String page;
#XmlElement(name="pages")
private String pages;
#XmlElement(name="perpage")
private String perpage;
#XmlElement(name="photo" , type=Photo.class)
private List<Photo> photoObject = new ArrayList<Photo>();
// constructors and getter setters.
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "photo")
public class Photo {
#XmlElement(name="id")
private String id;
#XmlElement(name="isfamily")
private String isfamily;
#XmlElement(name="title")
private String title;
#XmlElement(name="ispublic")
private String ispublic;
#XmlElement(name="owner")
private String owner;
#XmlElement(name="secret")
private String secret;
#XmlElement(name="server")
private String server;
#XmlElement(name="isfriend")
private String isfriend;
// constructors and setter getter
the response i get is null values in all these objects.
Resp [stat=null, photos=Photos [total=null, page=null, pages=null,
perpage=null, photo=]]
the value in String which i get is absolutely correct , but when i try to map my data to a POJO it starts giving error.
Other approach I used it to get the data directly in the object , like I have mentioned in my other question , but it also have some issues in that.
RestTemplate returns data in String but not populate list nested objects
if anyone can help in either one of these that'd be helpful.
As #f1sh already already commented,
your problems are caused by several issues within your POJO classes
In your XML response you have
<rsp stat="ok">...</rsp> (i.e. statis an XML attribute)
instead of <rsp><stat>ok</stat>...</rsp> (statbeing an XML element).
Therefore, in your Resp class the statproperty needs to annotated with
#XmlAttribute instead of #XmlElement.
For the same reason, in your Photos class, all properties
except for the photoObjectproperty need to be annotated with
#XmlAttribute instead of #XmlElement.
For the same reason, in your Photo class all properties
need to be annotated with #XmlAttribute instead of #XmlElement.
Some more best practices to consider:
In your Photos and Photo classes you can remove the #XmlRootElement annotation
because theses are not root elements.
Only the Resp class is a root element and therefore needs #XmlRootElement.
In your Photos class you should rename the photoObject property to photoObjects
(with a plural s) because it is a list. This would make the code easier to understand.
I found the solution .
The annotations #XMLAttribute and #XmlElements needed to be put on setters .
Did not work for getters and declarations.
i am trying to use one class to map the response i get from an XML request.
But the xml response differs, depending own some settings. For example in a response i get the tag "owner" which is filled with the ID of the owner object. If i add a setting in my request i will get back the full owner data, like the firstname and lastname. Now i want to map the owner tag to either a String variable or a Class depending on the response.
Example :
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "domain")
public class Response {
#XmlElement
private String name;
#XmlElement(name = "owner")
private String ownerSimple;
#XmlElement(name = "owner")
private Owner ownerComplex;
}
#XmlRootElement(name = "ownerc")
public class OwnerC {
#XmlElement
int id;
#XmlElement
String fname;
#XmlElement
String lname;
}
XML to map :
<response>
<name>Foo</name>
<owner>1234</owner> <!-- in this case it's only a id -->
</response>
<response>
<name>Foo</name>
<owner> <!-- in this case it's the owner class -->
<id>1234</id>
<fname>Jon</fname>
<lname>Doe</lname>
</owner>
</response>
You can use #XmlAnyElement(lax=true) to handle this use case. This annotation allows you to unmarshall any XML to a Java object (DOM Node). In a second step, it is possible to unmarshall the Node to the required Object
Response
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "domain")
public class Response {
#XmlElement
private String name;
#XmlAnyElement(lax=true)
private Object owner;
private String ownerSimple;
#XmlTransient
private Owner ownerComplex;
Owner
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "owner")
public class Owner {
#XmlElement
int id;
#XmlElement
String fname;
#XmlElement
String lname;
Unmarshaller
//Unmarshaller. Step 1 - Decodes Response and set a DOM Node at Owner
//Important. Owner class must not be present in JAXB context, letting next step to decode the object properly.
//Owner variable at Response class is annotated with #XmlTransient
JAXBContext jaxbContext = JAXBContext.newInstance(Response.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Response response = (Response) jaxbUnmarshaller.unmarshal(reader);
//Unmarshaller. Step 2. Convert Node to the suitable Object
//Considering both cases, simple-> String complex -> Owner Object.
String ownerSimple = ((Node)response.getOwner()).getFirstChild().getNodeValue();
if (ownerSimple != null){
response.setOwnerSimple(ownerSimple);
} else {
JAXBContext jaxbContextOwner = JAXBContext.newInstance(Owner.class);
Unmarshaller jaxbUnmarshallerOwner = jaxbContextOwner.createUnmarshaller();
Owner ownerComplex = (Owner) jaxbUnmarshallerOwner.unmarshal((Node)response.getOwner());
response.setOwnerComplex(ownerComplex);
}
//Marshaller to system.out. Your object is well mapped in both cases
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(rx, System.out);
I wish to have an XML structure like this:
<?xml version="1.0" encoding="UTF-8"?>
<MSG>
<CASE>
<Field1></Field1>
<Field2></Field2>
</CASE>
</MSG>
The problem is, with the #XmlElementWrapper, I need a collection of items but there will be only 1 case item. How can I have multiple root elements, for a single collection of elements? Preferably in a single class.
I want something like this, but it throws an exception.
#XmlRootElement( name="MSG")
public class XMLStructure {
#XmlElementWrapper(name="CASE")
#XmlElement(name = "Field1")
private String field1;
#XmlElementWrapper(name="CASE")
#XmlElement(name = "Field2")
private String field2;
}
In the EclipseLink MOXy implementation of JAXB (JSR-222) we have an #XmlPath extension that enables you to map this as:
#XmlRootElement( name="MSG")
#XmlAccessorType(XmlAccessType.FIELD)
public class XMLStructure {
#XmlPath("CASE/Field1/text()")
private String field1;
#XmlPath("CASE/Field2/text()")
private String field2;
}
For More Information
I have written more about the #XmlPath extension on my blog:
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
Is it possible to unmarshal a XML file like this:
<company id="bh23" name="imob">
<store>
<store-info id="2392">
<address>NYC</address>
<name>Imob's NYC 5th</name>
</store>
<products>
<product>
<name>keyboard</keyboard>
<price>2000</price>
</product>
<product>
<name>mouse</keyboard>
<price>1000</price>
</product>
</products>
</store>
<store />
</stores>
into classes like these:
#XmlElementRoot(name = "company")
public class Company {
#XmlAttribute (name = "id")
String id;
#XmlAttribute (name = "name")
String name;
#XmlElement (name = "store")
List<Store>stores;
//all the getters and setters
}
#XmlElementRoot (name = "store")
public class Store {
#XmlAttribute (name = "id")
String id;
#XmlElement (name = "address")
String address;
#XmlElement (name = "name")
String name;
#XmlElementWrapper (name = "products")
#XmlElement (name = "product")
List<Product>products;
//all the getters and setters
}
public class main {
public static void main (String args[]) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Company.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Company portfolio = (Company) jaxbUnmarshaller.unmarshal(new File(xmlUrlPath));
System.out.println(portfolio.toString());
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
I'm trying to "skip" or "jump" the node named "store-info" because I don't want to create another class just to keep the store's address and name, since it would be more simple to "append" both address and name to "Store" class.
Of course, when I run the code, the vars "address", "id" and "name" becomes null and only the list of products is correctly unmarshaled.
Is there a way to skip a node, merging their fields into another class? I'm avoiding (for "legal" purposes) the use of MOXy lib and their XPath annotation.
You could create a StAX filtered XMLStreamReader and have JAXB unmarshal that to ignore one or more XML elements.
http://docs.oracle.com/javase/7/docs/api/javax/xml/stream/XMLInputFactory.html#createFilteredReader%28javax.xml.stream.XMLStreamReader,%20javax.xml.stream.StreamFilter%29
Below is a link to a full example I gave in an answer to a similar question:
JAXB filtered parsing