Friday, December 02, 2011

Java Tip of the Day: Generic JAXB Map<K, V> XmlAdapter

There are a couple of well-known issues with using Map<K,V> with JAXB. First, JAXB does not like interfaces. Second, it considers a Map<K,V> to be a bean so it does not handle the internal implementation. The JAXB JIRA Issue is here JAXB-223

There are a number of articles published about how to adapt a Map<K,V> to work in JAXB. However, all of them I have seen are very specific mappings like Map<String,String>, or Map<Integer,String>, etc. None of them address the more specific generic case Map<K,V>. This example handles the more general case using generics. All the other examples I looked at online generally follow the example code from the XmlAdapter javadoc. The code snippet does not encapsulate the values, and generally does not adhere to good coding practices.

The process is to create an adapter is as follows:
  1. Create an mapping entry for Map<K,V>. In our case, we will create one called: MapEntryType<K, V> which will map our generic types.
  2. Next we create a value holder for our Map<K,V> which contains a List<MapEntryType<K, V>> of our entries. The value holder looks like MapType<K, V>
  3. Finally, we create an adapter which extends XmlAdapter. In our case, it is very long since I am using Java 6. In Java 7, we can use the diamond <> operator to shorten it. The adapter is XmlGenericMapAdapter<K, V> extends XmlAdapter<MapType<K, V>, Map<K, V>>.
Note: Please note that the MapEntryType<K, V> has the @XmlAccessorType(XmlAccessType.PROPERTY) set to property and the getters are annotated as @XmlElement.

The NetBeans 7.1 RC1 Apache Maven project can be downloaded here:

MapEntryType<K, V>

MapType<K, V>

XmlGenericMapAdapter<K, V> extends XmlAdapter<MapType<K, V>, Map<K, V>>

Here is the output from our adapter.

We take advantage of JAXB to provide automatic XML Schema definitions for our keys and values. The only problem with this approach is that it is very "chatty". It provides a schema definition for each key and value.

If you know that you are going to be using simple types, it is more appropriate to use a specific type rather than using generics. We would replace all of the <K, V> with <String, String>. This will remove all of the schema definitions and produce an output which is much cleaner. The String adapters are included in the source code for further review.


Unknown said...

I have used your implementation of generic MapAdapter. It works fine. I would like to thank you for your excellent work you shared with us. But I have problems making your MapAdapter work when List is used as maps value.

John Yeary said...

Is there something more specific in terms of what you are having issues with?

I imagine that you would need to modify the adapter to use something like Map<K, List<V>>. This is just a general guess though since you didn't provide details.

Anonymous said...

Hello John,

First, thank you for your post. It has been very helpful.

One question though : obviously, the XML marshal through JAXB of Map produces a parent tag for each ...entry of the map. This is ok for me.

But why is it different with a JSON marshal through JAXB, with the same StringAdapter ? I always get only one parent entry node, whether I have one entry in the map, or several... DO you have any idea ?

Example with one entry :

map { entry:{key:k,value:v}}

and with several entries :

map { entry:[{key:k,value:v}, {key:k,value:v}, {key:k,value:v}]}

Thanks in advance,

Popular Posts