Skip to main content

XmlAdapter in JAXB RI EA

Posted by kohsuke on April 22, 2005 at 3:50 PM PDT

Many people seem to have trouble understanding XmlAdapter/XmlJavaTypeAdapter.
I think it's at least partially because of the lack of documentation/samples,
but it might be that there's a problem in the design, and if so that's not good.

So today, I'm going to talk about XmlAdapter/XmlJavaTypeAdapter so that people
can make more informed discussions about it.

Motivation

Normally, JAXB tries to copy the in-memory representation straight to XML. If your object has fields a, b, and c, your XML will get elements like a, b, and c. This is a reasonable default, but it's also common for in-memory data representation to be vastly different from the on-the-wire representation.

A canonical example of this is a map. On XML, it's usually written like a list:

<br /><brochure><br />  <course id="cs501"><br />    <name>Software Engineering</name><br />    ...<br />  </course><br />  <course id="cs519"><br />    <name>Network Security</name><br />    ...<br />  </course><br />  ...<br /></brochure><br />

But in memory, it takes much more complex form that involves arrays, linked lists, and etc.
XmlAdapter is a mechanism intended to bridge this gap.

How It Works

XmlAdapter takes two classes as type parameters. One is the class that corresponds to the
XML representation. The other is the class that corresponds to the in-memory representation.
The former has to be a class that's bindable by JAXB, but the latter can be any Java type.
XmlAdapter defines two methods; the unmarshal method is used by unmarshallers to convert
the former to the latter, and the marshal method is used by marshallers to convert the latter
to the former.

For example, in the above example, suppose in memory you have:

<br />class Brochure {<br />  Map<String,Course> courses;<br />}<br /><br />class Course {<br />  String id;<br />  String name;<br />}<br />

Instead of printing the map in its 'raw' form, you'd want to print them like a list, so you write the following adapters:

<br />class CourseListAdapter extends XmlAdapter<Course[],Map<String,Course>> {<br />  public Map<String,Course> unmarshal( Course[] value ) {<br />    Map<String,Course> r = new HashMap<String,Course>();<br />    for( Course c : value )<br />      r.put(c.id,c);<br />    return r;<br />  }<br />  public Course[] unmarshal( Map<String,Course> value ) {<br />    return value.values().toArray(new Course[value.size()]);<br />  }<br />}<br />

And then you annotate your Java class like this:

@XmlRootElement(name="brochure")
class Brochure {
  @XmlJavaTypeAdapter(CourseListAdapter.class);
  @XmlElement(name="course")
  Map courses;
}

class Course {
  @XmlAttribute
  String id;
  @XmlElement
  String name;
}

This adapter annotation makes JAXB believe that the Brochure class looks like the following:

class Brochure {
  @XmlElement(name="course")
  Course[] courses;
}

This applies to the unmarshaller, the marshaller, and the schema generator.
When you run one of those like

marshaller.marshal( new Brochure(...), System.out );

..., adapters are used behind the scene to maintain this illusion for JAXB.
For example, during the unmarshalling, JAXB happily unmarshals Course[], and
when it think it set the value to the courses field, an adapter kicks in
and it replaces it to Map. Similarly, inside the marshaller,
when JAXB wants to get a value from the courses field, an adapter kicks in
again and converts the value from a Map to Course[] before JAXB sees the value.

The equivalent of this in Java serialization would be readResolve and writeResolve.

Sharing State Between Application and Adapter

Sometimes it's convenient to maintain application states inside adapters; for example,
if you have an adapter that converts string on XML into a java.lang.Class object, you might want
to have a ClassLoader in an adapter.

In JAXB, this is done by allowing applications to set configured instances of XmlAdapters
to the unmarshaller/marshaller. This is also an opportunity to pass in a sub-class of
the declared adapter, if you so wish.

If the application doesn't provide a configured instance, JAXB will create one by calling
the default constructor.

Issues

The approach illustrated so far requires that @XmlJavaTypeAdapter to be present on
every reference to a map. If you have multiple Maps all over your code, it's often
convenient to be able to apply an XmlAdapter globally, like
"use CourseListAdapter every Map you'll see in my classes".

If this were for only marshaller and unmarshaller, this can be easily done by, say,
adding those adapters to JAXBContext. But for the schema generator to work,
it has to be done declaratively, meaning that the schema generator can figure out exactly
what adapters apply without running any code.

Another issue is that you can't adapt the root object of the tree, because there's no
place to put @XmlJavaTypeAdapter. This is a lesser issue though, because you can always
invoke the adapter yourself before passing the value into JAXB.

(I think my blog is getting more and more like articles, which
is probably a bad thing)
Related Topics >>

Comments

Impossible without wrapper element

I am successfully using @XmlJavaTypeAdapter. I have just one problem, I cannot make your example work as it, I'm forced to wrap my collection inside a wrapper element like this:
<brochure>
<courses>
<course id="cs501">
<name>Software Engineering</name>
...
</course>
<course id="cs519">
<name>Network Security</name>
...
</course>
...
</courses>
</brochure>


With this mapping:

@XmlRootElement(name="brochure")
class Brochure {
@XmlJavaTypeAdapter(CourseListAdapter.class);
@XmlElement(name="courses")
Map courses;
}

class CourseListWrapper {
@XmlElement
List innerList;
}

class Course {
@XmlAttribute
String id;
@XmlElement
String name;
}

And this adapter: class CourseListAdapter extends XmlAdapter<CourseListWrapper,Map<String,Course>>{
...
}


Moreover, this is what is described in the JavaDoc. Does your example really work or do we systematically need a wrapper element?