The XmlAdapter mechanism in JAXB ensures that there is no such thing as an unmappable class (with a little programmatic help). However there appears to be some confusion on how to use XmlAdapter, below is the general concept:
- 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).
1 2 3 4 5 6 7 8 9 | import java.util.ArrayList; import java.util.List; public class MyMapType { public List<MyMapEntryType> entry = new ArrayList<MyMapEntryType>(); } |
and
1 2 3 4 5 6 7 8 9 10 11 12 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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.
No comments:
Post a Comment