- Identify the unmappable class
- Create an equivalent class that is mappable
- Create an XmlAdapter to convert between unmappable and mappable objects
- Specify the XmlAdapter
1. Identify the Unmappable Class
In this example the unmappable class is java.util.Map.
2. Create an Equivalent Class that is Mappable
Map could be represented by an object (MyMapType), that contained a list of objects with two properties: key and value (MyMapEntryType).
import java.util.ArrayList; import java.util.List; public class MyMapType { public List<MyMapEntryType> entry = new ArrayList<MyMapEntryType>(); }
and
import javax.xml.bind.annotation.XmlValue; import javax.xml.bind.annotation.XmlAttribute; public class MyMapEntryType { @XmlAttribute public Integer key; @XmlValue public String value; }
3. Create an XmlAdapter to Convert Between Unmappable and Mappable Objects
The XmlAdapter<ValueType, BoundType> class is responsible for converting between instances of the unmappable and mappable classes. Most people get confused between the value and bound types. The value type is the mappable class, and the bound type is the unmappable class.
import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.xml.bind.annotation.adapters.XmlAdapter; public final class MyMapAdapter extends XmlAdapter<MyMapType,Map<Integer, String>> { @Override public MyMapType marshal(Map<Integer, String> arg0) throws Exception { MyMapType myMapType = new MyMapType(); for(Entry<Integer, String> entry : arg0.entrySet()) { MyMapEntryType myMapEntryType = new MyMapEntryType(); myMapEntryType.key = entry.getKey(); myMapEntryType.value = entry.getValue(); myMapType.entry.add(myMapEntryType); } return myMapType; } @Override public Map<Integer, String> unmarshal(MyMapType arg0) throws Exception { HashMap<Integer, String> hashMap = new HashMap<Integer, String>(); for(MyMapEntryType myEntryType : arg0.entry) { hashMap.put(myEntryType.key, myEntryType.value); } return hashMap; } }
4. Specify the XmlAdapter
The @XmlJavaTypeAdapter annotation is used to specify the use of the XmlAdapter. Below it is specified on the map field on the Foo class. Now during marshal/unmarshal operations the instance of Map is treated as an instance of MyHashMapType.
import java.util.HashMap; import java.util.Map; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Foo { @XmlJavaTypeAdapter(MyMapAdapter.class) Map<Integer, String> map = new HashMap<Integer, String>(); public Map getMap() { return map; } public void setMap(Map map) { this.map = map; } }
In upcoming posts I'll describe extensions in the EclipseLink JAXB (MOXy) implementation that reduce the dependency on XmlAdapter.
Further Reading
If you enjoyed this post, then you may also be interested in:
- JAXB and Immutable Objects
In this post an XmlAdapter is used map a domain object with a mult-argument constructor and fields marked final. This example also demonstrates how the @XmlJavaTypeAdapter annotation can be used at the type level.
- @XmlTransformation - Going Beyond XmlAdapter
In this post an EclipseLink MOXy extension (@XmlTransformation) is used instead of @XmlAdapter to provide a little more flexibility for an interesting use case.
Nicely explained!
ReplyDeleteBut I have even more subtle problem. I have complex data structures such as Vector > and HashMap> i.e. vector inside vector and vector inside hashmaps. Could you please help me on how I should marshall/unmarshall?
The process would be very similar.
ReplyDeleteVector Inside a HashMap
If we consider you case with a Vector inside a HashMap. You would change the MyMapEntryType so that the value property was a Vector.
Vector Inside a Vector
For this use case your intermediate object would be a Vector of Value objects, each Value Object would have a value property representing the nested Vector.
Great post Blaise!
ReplyDeleteI'm using XMLAdapter with a List, but I'm having a problem. Marshalling the entities of the List, the < and > tags are replaced by < and >
A Customer as some attibutes (id, name, ...) and a DocumentSet (basically a List). This DocumentSet is extendind the XMLAdapter where I do this;
public class DocumentSetAdapter extends XmlAdapter {
public String marshal(DocumentSet val) throws Exception {
java.io.StringWriter sb = new java.io.StringWriter();
for (Document d : val.getDocument()) {
if (d instanceof IdentificationCard) {
sb.append(((IdentificationCard) d).toXml(false));
} else if (d instanceof DrivingLicense) {
sb.append(((DrivingLicense) d).toXml(false));
} else if (d instanceof Passport) {
sb.append(((Passport) d).toXml(false));
}
}
return sb.toString();
}
IdentificationCard and DrivingLicense extend Document.
The toXML method does the marshalling, and is the same in every class.
The one is called first is the Customer and is like this:
public String toXml(boolean header) throws javax.xml.bind.JAXBException {
java.io.StringWriter sw = new java.io.StringWriter();
javax.xml.bind.JAXBContext jc = javax.xml.bind.JAXBContext.newInstance(Customer.class);
javax.xml.bind.Marshaller m = jc.createMarshaller();
m.setProperty(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(this, sw);
return sw.toString();
}
The output xml is:
customer1
Carlos
EMPLOYEE
premium
active
2010-11-12T16:20:48.752Z
<identificationCard>
<id>1</id>
<description>BI</description>
<issueDate>2010-11-12T16:20:48.759Z</issueDate>
<expireDate>2010-11-12T16:20:48.759Z</expireDate>
<birthPlace>Aveiro</birthPlace>
<identificationNumber>123131231</identificationNumber>
</identificationCard><drivingLicense>
<id>2</id>
<description>NIF</description>
<issueDate>2010-11-12T16:20:48.759Z</issueDate>
<expireDate>2010-11-12T16:20:48.759Z</expireDate>
<licenseNumber>9654477</licenseNumber>
<vehicleCat>A</vehicleCat>
</drivingLicense>
and the correct would be:
customer1
Carlos
EMPLOYEE
premium
active
2010-11-12T16:26:09.891Z
1
BI
2010-11-12T16:26:09.893Z
2010-11-12T16:26:09.893Z
Aveiro
123131231
2
NIF
2010-11-12T16:26:09.893Z
2010-11-12T16:26:09.893Z
9654477
A
Could you help me with this?
Best regards,
Carlos
Hi Carlos,
ReplyDeleteWhen using XmlAdapter you are converting from an object that JAXB cannot map to one that it can. In your example you are converting to a String that contains XML markup. This is causing JAXB to escape some of the characters. Instead you should convert to an object that would produce the desired XML.
Some of the details of your message were lost due to the XML escaping aspect of this blog. Feel free to message me from the blog with about this issue:
- http://bdoughan.blogspot.com/p/contact_01.html
-Blaise
Very nice article!
ReplyDeleteI was wondering, if my JAXB annotated classes are generated based on a given XSD file, how can I specify my adapters, knowing that every time I changed the XSD file, all classes will also be regenerated?
Hi Bogdan,
ReplyDeleteThe XmlAdapter mechanism is used when starting with Java classes and you have an unmappable class. When you start from an XML schema, the JAXB schema compiler tool (XJC) generates compatible classes, no XmlAdapter is necessary.
-Blaise
Hi Carlos,
ReplyDeleteThank you for sending me the sample code. I have just emailed you an example that demonstrates how XmlAdapter could be used.
I do not recommend the approach that you are attempting where the adapted type is a String that contains XML markup. You may be able to make this work with streams, but it will not work when dealing with DOM/SAX/StAX inputs and outputs.
-Blaise
Hi Blaise,
ReplyDeleteI'm encoutering an issue, where the objects generated from JAXB (through Maven) having no no-arg constructors on Windows couldn't work when they're deployed in server on Linux during creation of JAXBContext - complaining of no no-arg public constructor. Is that something XmlAdapter could resolve?
Thanks,
- John
Hi John,
ReplyDeleteIt is common to use an XmlAdapter for classes that do not have a no-arg constructor. However, JAXB should not be creating classes without a no-arg constructor. Feel free to send me more details about your setup through the "Contact Me" page on this blog.
-Blaise
The MyMapAdapter does not compile
ReplyDeleteimcompatible types
found : java.lang.Object
required: java.util.Map.Entry
for (Entry entry : arg0.entrySet()) {
The compilation issue should be fixed now. Looks like I forgot to escape the '<' characters in my code samples. Thanks for identifying the issue.
ReplyDeleteIf you do *not* use an Adapter, this works:
ReplyDelete@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Simple {
private Map map = new HashMap();
}
This does not:
@XmlRootElement
public class Simple {
@XmlElement
private Map map = new HashMap();
}
Why is this?
rmuller,
ReplyDeleteThat appears to be a bug in the JAXB reference implementation (Metro). This issue does not occur when you use EclipseLink JAXB (MOXy).
-Blaise
Thank you. These blogs are the best tutorial for Moxy.
ReplyDeleteNow what I'd really like to do is convert the map "directly", i.e., eliminate the key and value elements, by using the keys for the element names, and the values for the element contents. I've tried returning a DynamicEntity in the marshall methodd, but not successfully. And I don't know of an Annotation that lets me specify a field as providing the element name. Am I missing something obvious?
Class MyMapAdapter won't compile. Here is the corrected version:
ReplyDeleteimport java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public final class MyMapAdapter extends
XmlAdapter> {
@Override
public MyMapType marshal(Map arg0) throws Exception {
MyMapType myMapType = new MyMapType();
for (Entry entry : arg0.entrySet()) {
MyMapEntryType myMapEntryType = new MyMapEntryType();
myMapEntryType.key = entry.getKey();
myMapEntryType.value = entry.getValue();
myMapType.entry.add(myMapEntryType);
}
return myMapType;
}
@Override
public Map unmarshal(MyMapType arg0) throws Exception {
HashMap hashMap = new HashMap();
for (MyMapEntryType myEntryType : arg0.entry) {
hashMap.put(myEntryType.key, myEntryType.value);
}
return hashMap;
}
}
runnable Demo:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.getMap().put(1, "one");
foo.getMap().put(2, "two");
foo.getMap().put(3, "three");
try {
JAXBContext ctx = JAXBContext.newInstance(Foo.class);
Marshaller m = ctx.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(foo, System.out);
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
Thanks, your article was helpful.
Hi
ReplyDeleteThank you for these wonderful tutorials.
I have an issue, that is
I'm trying to parse the XML response into POJO(may not be reversely).
For this I'm using Jaxb2Marshaller with MarshallingHttpMessageConverter through Spring RestTemplate [postForObject].
My setup is fine to map the child with its direct parent element using like @XmlElementWrapper.
But I wanna to map elements like by its name, That is I need only some of the elements from the whole xml. Is this possible with xmladapter or by somthing else. pls let me know ASAP. I hope you understand my case.
Thanks
Prakash.
Hi Prakash,
ReplyDeleteThe following may be of help:
- XPath Based Mapping - Geocode Example
Feel free to contact me with additional details:
- Contact Me
-Blaise
Hi Blaise,
ReplyDeleteIts an interesting pattern, thanks for sharing. I would like to ask you if jaxb can be used to map a hashmap like
Element key=val key=val
where Element would be root element and key,vals are attributes. Can names of XmlAttribute be dynamic? Even if you can point me in the right direction it would be of great help. Cheers!
You can use @XmlAnyAttribute for this use case. For an example see:
ReplyDelete- http://stackoverflow.com/questions/5317172/how-to-use-hashmap-properties-with-jaxb/5331471#5331471
-Blaise
Hi Carlo,
ReplyDeleteThank you for sending the corrected code. I apologize in the delay for posting it.
-Blaise
Hi, Blaise,
ReplyDeleteNice article.
I am facing an interesting problem. I need to unmarshall following into an ArrayList of Customer objects.
The problem is that the top level object is ArrayList which is not mappable.
I created a class extending ArrayList and annotate it like this.
@XmlRootElement(name="collection")
public class MyArrayList extends ArrayList {
}
But this does not help. Is there a way to use XmlAdapter to take care of this problem?
Hi, Blaise,
ReplyDeleteI think I didn't ask this clear in my first comment.
The problem is that the entities in the collection is of generic type. So, it could be Customer or it could be Vendor.
Is there a way to achieve this?
Thanks,
Xianguan
I have the following collection class. Note the XmlElement(name="yes"), how can I not specify the element name and let the type decide on the name of the element?
ReplyDelete@XmlRootElement(name="collection")
public class MyCollections {
private List items;
@XmlElement(name="yes")
public List getItems() {
return items;
}
public void setItems(List items) {
this.items = items;
}
}
Hi Xianguan,
ReplyDeleteIf you know the types in advance I would recommend using the @XmlElements annotation. This corresponds to the choice concept in XML schema:
- JAXB and XSD Choice: @XmlElements
However if you don't know the types if advance you can leverage @XmlAnyElement(lax=true) for this:
- Using @XmlAnyElement to Build a Generic Message
-Blaise
@XmlAnyElement(lax=true) worked beautifully for me. Thank you so much!
ReplyDeleteFrom what I can tell, java.util.Calendar loses timezone information when it is marshalled/unmarshalled to XML. In particular, it loses timezone information because the timezone is expressed as an offset from GMT like this: "-07:00" rather than the more proper ID form "America/Los_Angeles". I can envision a solution to this problem using XMLAdapter and the creation of a MyCalendar class, but wanted to know if this is the best way to tackle this issue.
ReplyDeleteBelow is the code I used to properly serialize a java.util.Calendar along with its timezone. This whole problem brings up an interesting question: Does a calendar represent an instant in time (i.e. millis since epoch in GMT), or does it represent that instant in time at a particular location (a timezone).
ReplyDelete@XmlAccessorType(XmlAccessType.NONE)
public class SerializableCalendar {
@XmlElement
private Calendar calendar = null;
@XmlElement
private String tzID = null;
public Calendar getCalendar() {
return calendar;
}
public void setCalendar(Calendar calendar) {
this.calendar = calendar;
}
public String getTzID() {
return tzID;
}
public void setTzID(String tzID) {
this.tzID = tzID;
}
}
public final class SerializableCalendarAdapter extends XmlAdapter {
@Override
public Calendar unmarshal(SerializableCalendar sc) throws Exception {
Calendar c = sc.getCalendar();
c.setTimeZone(TimeZone.getTimeZone(sc.getTzID()));
return c;
}
@Override
public SerializableCalendar marshal(Calendar c) throws Exception {
SerializableCalendar sc = new SerializableCalendar();
sc.setCalendar(c);
sc.setTzID(c.getTimeZone().getID());
return sc;
}
}
Hi Ken,
ReplyDeleteJAXB marshals time zone information based on the XML schema representation of 'Z' for UTC and '+hh:mm' or '-hh:mm' for offsets.
xsd:time formats:
hh:mm:ssZ
hh:mm:ss+hh:mm
hh:mm:ss-hh:mm
An XmlAdapter is a reasonable way to provide alternate representations.
-Blaise
Blaise and co: this was really helpful, thank you all (Blaise especially.)
ReplyDeleteOne quick note: it's interesting to see how JAXB treats annotations in the MyMapType wrapper class. Maybe say a word or two about that?)
Gr8 article blaise,
ReplyDeleteI am having trouble to figure out how to tackle above issue?
I want to create a class for xml file containing element with a pipe seperated data field with new line character to seperate multiple record.
For eg:
1234 |HERNANDEZ MICHEAL E | 9195672232 | 01/10/2011 08:00:00|Y|Y222|1|ETR|Lightning \n
2345 |JONATHAN SILOH|9019876534| 01/10/2011 08:00:00|Y|Y297|2|ETR|Overload \n
How could I track this element information efficiently using JAXB API?
Thank You,
Rushikey
Hi Rushikey,
ReplyDeleteYou could use an XmlAdapter for this use case. The bound type (unmappable object) will be whatever you will convert that value to, and the value type (mappable object) will be java.lang.String. Then in the adapter you will need to write the code to convert to/from the piped data.
-Blaise
Blaise,
ReplyDeleteVery good presentation and very easy to understand. Lot of thanks for spending your time to help us.
Thanks Blaise!!
ReplyDeleteI was trying to unmarshal Clob since yesterday. This really helped.
Thanks,
Sid
Thank you Blaise as well.
ReplyDeleteI am interested in the order of the items inside a list but I see that the marshalling/unmarshalling process does not respect the original order of the items.
For example if I construct an object list with items a, b, c and I change the order to c, b, a the exported XML will have the original order.
How I will make sure that the order of the items of my list in the exported XML will be the same with the order of the Java list object (and vice versa)?
Do I have to use adapters for this?
Thank you in advance.
===
Apartment for rent in Litochoro --> www.litochoro.eu
===
Hi Klearchos,
ReplyDeleteAssuming that you are representing your collection using the java.util.List type that maintains order, the marshalled XML should match this order.
Could you send me more details via my "Contact Me" page?
-Blaise
thank you !
ReplyDeleteit'll be perfect if we could see the xsd file.
Because I'm doin' all this, but something must be wrong in my xsd and I thought I could look at yours in the example
Hi,
DeleteI'm confused on why are you looking for the XML schema. Are you looking to generate this model? This example is specifically aimed at starting from a Java model.
-Blaise
I thought as specified in javadoc http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/2.0/api/javax/xml/bind/annotation/adapters/XmlAdapter.html
Deletexmladapter only works with an xsd file, but I might have misunderstood..
It may be step 2 from that link (http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/2.0/api/javax/xml/bind/annotation/adapters/XmlAdapter.html) that is causing some confusion. What that step really means is you need to decide what you want the XML to look like, and then you need to create value objects that correspond to the desired XML format.
Delete-Blaise
Hi,
ReplyDeleteIs there any way to declare required and nillable as if @XmlElementWrapper(required = true, nillable=true) Collection types;
Hi Jin,
DeleteNot sure I understand your question. @XmlElementWrapper does allow you to specify required and nillable for Collection types. The required property corresponds to schema generation. The nillable aspect in addition to affected schema generation will cause the wrapper element to be written with the xsi:nil="true" attribute for null collections. Without this setting the wrapper element is not marshalled.
-Blaise
I want to implement JAXB for Test class,i find that if variables are final,is it not possible to inject JAXB? Will XmlAdapter help in this case?
ReplyDeletepublic class Test implements Serializable {
private final int limit;
private Map results;
private final Sub sub;
@XmlElementWrapper(name = "item") //tried with this also,but not successfull
@XmlAnyElement
private final List item;
}
public class Item implements Serializable {
private Base base;// Interface
public Item(final Base base) {
this.base = base;
}
}
The following may help:
Delete- JAXB and Immutable Objects
I am also aware of this question on Stack Overflow. It is probably easiest to tackle in on that forum:
- final property to bind JAXB
-Blaise
Based off my (admittedly short) research into XMLAdapter, it seems the annotation used to identify the adapter cannot be parametrized. Is this truly the case? It seems onerous to define an adapter class for each type of parameter combinations used with the Map class. If this is the case, do you have any thoughts on why this limitation exists? I am an not well versed enough on the inner workings of Java to guess at the Apache folks' reasoning.
ReplyDeleteHi Matthew,
DeleteOne reason for the limitation is that the Java language does not allow you to specify a parameterized type as a value on an annotation.
You may find the following post helpful:
- Java Tip of the Day: Generic JAXB Map XmlAdapter
BTW - Apache did not participate in the development of the JAXB (JSR-222) specification. There were representatives from: Sun, Oracle (myself), IBM, BEA, SAP, and quite a few other companies.
-Blaise
Hi Blaise,
ReplyDeleteAbove you created XMLAdapter using HashMap can we do samething using ArrayList.
Thanks in Advance
Hi,
DeleteYou can use an XmlAdapter with an ArrayList property, but the XmlAdapter will be applied to each item in the collection instead of the collection as a whole.
-Blaise
Hello Blaise,
ReplyDeleteCan you please tell me what data type should I mention in my XSD file while using adapters.
Will it be the user defined java/util/Map equivalent class or something else.
Thanks
Hello,
DeleteXmlAdapters are mainly used when starting from Java objects. Below is an example that demonstrates how they can be applied when starting from an XML schema.
- XML Schema to Java - Generating XmlAdapters
-Blaise
Hi Blaise,
ReplyDeletecan we use an Adapter Class for sql Connection interface ? As we know JAXB can not handle Interface.
- Gopi
Hi Gopi,
DeleteYes you can use this approach for handling interfaces. Below is another article you may find interesting about supporting interfaces in JAXB.
- JAXB and Interface Fronted Models
-Blaise
Hello Blaise,
ReplyDeleteThank you for your valuable blog posts !
On the subject of XmlAdapter, I was wondering if you could have a look at my SO question ?
http://stackoverflow.com/questions/19842179/how-to-ignore-disable-revert-override-jaxb-class-level-xmljavatypeadapter-in-ce
I'd like to have an elegant implementation that is along the lines of JAXB's philosophy.
Thank you in advance !
-C
I have posted an answer on your Stack Overflow question:
Delete- http://stackoverflow.com/a/19940091/383861
You can find more information in the following link:
- Mixing Nesting and References with JAXB's XmlAdapter
Hi Blaise,
ReplyDeleteIs it possible to use an external value in the XmlAdapter? I have the following pojo
class Task{
int id;
Timezone timezone;
@XmlJavaTypeAdapter(type = DateTime.class, value = DateTimeAdapter.class)
DateTime startDate;
....
}
And the XmlAdapter
public class DateTimeAdapter extends XmlAdapter {
private static final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
public DateTime unmarshal(String dateString) throws Exception {
return (StringUtils.isNotEmpty(dateString) ? dateFormat.withZoneUTC().parseDateTime(dateString) : null);
}
public String marshal(DateTime dateTime) throws Exception {
return (dateTime != null ? dateFormat.print(dateTime) : null);
}
}
I would like to use the timezone set in the same pojo when marshalling and unmarshalling the DateTime. Can I somehow pass the timezone to the marshaller?
Hi Banu,
DeleteBy default a JAXB impl will create a new instance of the XmlAdapter each time it is used. If you want it to be stateful you can set a configured instance via the setAdapter methods on Marshaller/Unmarshaller.
-Blaise