Unmarshalling with JAXB was little complicated than marshaling. I
started with my previous post and tried keeping my classes simple by using
minimal set of annotations. Our goal is to create set of Java classes to parse
below XML document.
<?xml version="1.0"
encoding="UTF-8" standalone="yes"?>
<Album>
<Name>My trip to Hawai</Name>
<Date>11/03/2012</Date>
<Pictures>
<Picture type="JPEG">
<Name>Foodoo</Name>
<Place>At Beach</Place>
<Time>11/01/2013 11:15:39 AM</Time>
</Picture>
<Picture type="GIF">
<Name>Dilloo</Name>
<Place>In hotel</Place>
<Time>11/01/2013 07:50:56 PM</Time>
</Picture>
</Pictures>
</Album>
Following the XML structure, I created three classes
Album.java
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="Album")
public class Album {
private String name;
private Date date;
private Pictures pictures;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public Date getDate() {
return date;
}
public void setDate(Date value) {
this.date = value;
}
public Pictures getPictures() {
return pictures;
}
public void setPictures(Pictures value) {
this.pictures = value;
}
}
Picture.java
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
public class Picture {
private String name;
private String place;
private Date time;
@XmlAttribute
protected String type;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getPlace() {
return place;
}
public void setPlace(String value) {
this.place = value;
}
public Date getTime() {
return time;
}
public void setTime(Date value) {
this.time = value;
}
public String getType() {
return type;
}
public void setType(String value) {
this.type = value;
}
}
Pictures.java
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
public class Pictures {
private List<Picture> picture;
public List<Picture> getPicture() {
if (picture == null) {
picture = new ArrayList<Picture>();
}
return this.picture;
}
public void setPicture(List<Picture> picture) {
this.picture = picture;
}
}
After setting up all my classes, created an unmarshaler for parsing the XML document.
JAXBUnmarshalar
import java.io.FileInputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class JAXBUnmarshalar {
public static void main(String s[]) throws Exception{
//A JAXBContext instance is created for handling classes generated in the default package. Since Album.class is top level Java class, we could specify this class while instantiating the context instead of package
JAXBContext jc = JAXBContext.newInstance(Album.class);
Unmarshaller u = jc.createUnmarshaller();
Album album = (Album)u.unmarshal(JAXBUnmarshalar.class.getResource("Album.xml"));
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class JAXBUnmarshalar {
public static void main(String s[]) throws Exception{
//A JAXBContext instance is created for handling classes generated in the default package. Since Album.class is top level Java class, we could specify this class while instantiating the context instead of package
JAXBContext jc = JAXBContext.newInstance(Album.class);
Unmarshaller u = jc.createUnmarshaller();
Album album = (Album)u.unmarshal(JAXBUnmarshalar.class.getResource("Album.xml"));
/******** Unmarshalling completed at this point. Let's display the outcome *************/
Marshaller jaxbMarshaller = jc.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(album, System.out);
}
}
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(album, System.out);
}
}
Executing the unmarshaler produced below output.
Output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Album/>
<Album/>
Our unmarshaler failed to read all the sub element of <Album> and this output was not as per our expectation. I assumed that attribute names in our Java classes will map to XML element in spite of differences in their case (i.e. private String name; will map to XML element <Name>) and that assumption was incorrect. I didn't wanted to change attribute names of my Java classes, that was too much of work. After some googling, I reached at this link in JAXB tutorial.
Updated all my class attribute with @XmlElement annotation mentioning corresponding XML element name. After modification, my class definition changed to
Album.java
@XmlRootElement(name="Album")
public class Album {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Date")
private Date date;
@XmlElement(name="Pictures")
private Pictures pictures;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}…. …. … ..//public accessor for all other ‘private’ attributes goes here
}
After compiling the Java classes, when I executed JAXBUnmarshalar, got another set of errors:
Class has two properties of the same name "name"
this problem is related to the following location:
at public java.lang.String Album.getName()
at Album
this problem is related to the following location:
at private java.lang.String Album.name
at Album
Time for another set of annotations. See this link or below excerpts from JAXB tutorial.
6.2.5 Controlling Element Selection:
XmlAccessorType
,
XmlTransient
If JAXB binds a class to XML, then, by default, all public members will be bound, i.e., public getter and setter pairs, or public fields. Any protected, package-visible or private member is bound if it is annotated with a suitable annotation such as
XmlElement
or XmlAttribute
. You have several possibilities to
influence this default behaviour.
In our case 'name' attribute is bound to to JXB through 'public' accessor methods
public String get/setName(); and
@XmlElement
private String name;
We can solve this problem in two ways, either place the @XmlElement annotation on accessor method or declare @XmlAccessorType annotation on top of the class.
I opted for 2nd option and annotated my class with @XmlAccessorType(XmlAccessType.FIELD)
Resulting class definition was same as previous except for addition of a new annotation.
@XmlRootElement(name="Album")
@XmlAccessorType(XmlAccessType.FIELD)
public class Album {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Date")
private Date date;
@XmlElement(name="Pictures")
private Pictures pictures;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Album {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Date")
private Date date;
@XmlElement(name="Pictures")
private Pictures pictures;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
…. …. … ..//public accessor for all other ‘private’ attributes goes here
}
At this point, after executing my JAXBMarshaler, output XML was close to our original document:
output
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Album>
<Name>My trip to Hawai</Name>
<Pictures>
<Picture type="JPEG">
<Name>Foodoo</Name>
<Place>At Beach</Place>
</Picture>
<Picture type="GIF">
<Name>Dilloo</Name>
<Place>In hotel</Place>
</Picture>
</Pictures>
</Album>
<Album>
<Name>My trip to Hawai</Name>
<Pictures>
<Picture type="JPEG">
<Name>Foodoo</Name>
<Place>At Beach</Place>
</Picture>
<Picture type="GIF">
<Name>Dilloo</Name>
<Place>In hotel</Place>
</Picture>
</Pictures>
</Album>
The <time> element was still missing and it seems JAXB failed to parse that field in spite of everything looked good.
After some googling, I realized that JAXB didn't like date format specified in my XML document
<Time>11/01/2013 07:50:56 PM</Time> <!- MM/dd/yyyy hh:mm:ss-->
Thanks to this stakoverflow post, that indicates JAXB likes date values in this (
"yyyy-MM-dd'T'HH:mm:ss") format.
"yyyy-MM-dd'T'HH:mm:ss") format.
If we like to make JAXB understand our format, we need to provide some more information by using '@XmlJavaTypeAdapter' annotation. This annotation is defined as 'For some Java container types JAXB has no built-in mapping to an XML
structure. Also, you may want to represent Java types in a way that is
entirely different from what JAXB is apt to do. Such mappings require an
adapter class, written as an extension of
XmlAdapter<XmlType,ApplType>
from the package
javax.xml.bind.annotation.adapters
.' See more about this annotation here.
I modified my 'Picture' class to have this additional information.
@XmlJavaTypeAdapter(DateAdapter.class)
@XmlElement(name="Time")
private Date time;
@XmlElement(name="Time")
private Date time;
This completed our unmarshalling task, see updated class definitions below.
Album.java
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="Album")
@XmlAccessorType(XmlAccessType.FIELD)
public class Album {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Date")
private Date date;
@XmlElement(name="Pictures")
private Pictures pictures;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public Date getDate() {
return date;
}
public void setDate(Date value) {
this.date = value;
}
public Pictures getPictures() {
return pictures;
}
public void setPictures(Pictures value) {
this.pictures = value;
}
}
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name="Album")
@XmlAccessorType(XmlAccessType.FIELD)
public class Album {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Date")
private Date date;
@XmlElement(name="Pictures")
private Pictures pictures;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public Date getDate() {
return date;
}
public void setDate(Date value) {
this.date = value;
}
public Pictures getPictures() {
return pictures;
}
public void setPictures(Pictures value) {
this.pictures = value;
}
}
Picture.java
import java.util.Date;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlAccessorType(XmlAccessType.FIELD)
public class Picture {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Place")
private String place;
@XmlJavaTypeAdapter(DateAdapter.class)
@XmlElement(name="Time")
private Date time;
@XmlAttribute
protected String type;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getPlace() {
return place;
}
public void setPlace(String value) {
this.place = value;
}
public Date getTime() {
return time;
}
public void setTime(Date value) {
this.time = value;
}
public String getType() {
return type;
}
public void setType(String value) {
this.type = value;
}
}
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlAccessorType(XmlAccessType.FIELD)
public class Picture {
@XmlElement(name="Name")
private String name;
@XmlElement(name="Place")
private String place;
@XmlJavaTypeAdapter(DateAdapter.class)
@XmlElement(name="Time")
private Date time;
@XmlAttribute
protected String type;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getPlace() {
return place;
}
public void setPlace(String value) {
this.place = value;
}
public Date getTime() {
return time;
}
public void setTime(Date value) {
this.time = value;
}
public String getType() {
return type;
}
public void setType(String value) {
this.type = value;
}
}
Pictures.java
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.FIELD)
public class Pictures {
@XmlElement(name="Picture")
private List<Picture> picture;
public List<Picture> getPicture() {
if (picture == null) {
picture = new ArrayList<Picture>();
}
return this.picture;
}
public void setPicture(List<Picture> picture) {
this.picture = picture;
}
}
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
@XmlAccessorType(XmlAccessType.FIELD)
public class Pictures {
@XmlElement(name="Picture")
private List<Picture> picture;
public List<Picture> getPicture() {
if (picture == null) {
picture = new ArrayList<Picture>();
}
return this.picture;
}
public void setPicture(List<Picture> picture) {
this.picture = picture;
}
}
DateAdaptor.java
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat(
"MM/dd/yyyy HH:mm:ss");
@Override
public String marshal(Date v) throws Exception {
return dateFormat.format(v);
}
@Override
public Date unmarshal(String v) throws Exception {
return dateFormat.parse(v);
}
}
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, Date> {
private SimpleDateFormat dateFormat = new SimpleDateFormat(
"MM/dd/yyyy HH:mm:ss");
@Override
public String marshal(Date v) throws Exception {
return dateFormat.format(v);
}
@Override
public Date unmarshal(String v) throws Exception {
return dateFormat.parse(v);
}
}
JAXBUnmarshalar
import java.io.FileInputStream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class JAXBUnmarshalar {
public static void main(String s[]) throws Exception{
//A JAXBContext instance is created for handling classes generated in the default package. Since Album.class is top level Java class, we could specify this class while instantiating the context instead of package
JAXBContext jc = JAXBContext.newInstance(Album.class);
Unmarshaller u = jc.createUnmarshaller();
Album album = (Album)u.unmarshal(JAXBUnmarshalar.class.getResource("Album.xml"));
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class JAXBUnmarshalar {
public static void main(String s[]) throws Exception{
//A JAXBContext instance is created for handling classes generated in the default package. Since Album.class is top level Java class, we could specify this class while instantiating the context instead of package
JAXBContext jc = JAXBContext.newInstance(Album.class);
Unmarshaller u = jc.createUnmarshaller();
Album album = (Album)u.unmarshal(JAXBUnmarshalar.class.getResource("Album.xml"));
/******** Unmarshalling completed at this point. Let's display the outcome *************/
Marshaller jaxbMarshaller = jc.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(album, System.out);
}
}
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(album, System.out);
}
}
Executing JAXBUnmarshalar produced below output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Album>
<Name>My trip to Hawai</Name>
<Pictures>
<Picture type="JPEG">
<Name>Foodoo</Name>
<Place>At Beach</Place>
<Time>11/01/2013 11:15:39</Time>
</Picture>
<Picture type="GIF">
<Name>Dilloo</Name>
<Place>In hotel</Place>
<Time>11/01/2013 07:50:56</Time>
</Picture>
</Pictures>
</Album>
Overall it was little difficult to start with unmarshalling, however by using some of the simple annotations it become very easy to parse the documents.
References:
No comments:
Post a Comment