Search code examples
jsonjaxbjerseyjackson

jersey (+ jackson) map field serialization


I have a simple jersey web service and I'd like to consume / produce objects that contain map fields, like

@XmlElement
private Map<String,String> properties;

if this string goes into the web service,

{ properties: { key1: val1, key2: val2 )}

the properties field is deserialized as null with no errors. the same JSON goes in and out of GSON no problems, and in the short term I solved this by having jersey consume produce strings and using GSON to serialize / deserialize the JSON.

any ideas?


Solution

  • Jersey uses JAXB for serialization. JAXB can not serialize a Map as there is no XML type for Java type Map. Also, Map is an interface and JAXB does not like interfaces. If you are using JAXBJackson bridge to marshal, you will run into issue.

    You will need to create an adapter like below and annotate your Map property with

    @XmlJavaTypeAdapter(MapAdapter.class)
    private Map<String,String> properties;
    
    @XmlSeeAlso({ Adapter.class, MapElement.class })
    public class MapAdapter<K,V> extends XmlAdapter<Adapter<K,V>, Map<K,V>>{
    
    
      @Override
      public Adapter<K,V> marshal(Map<K,V> map) throws Exception {
    
        if ( map == null )
          return null;
    
        return new Adapter<K,V>(map);
      }
    
    
      @Override
      public Map<K,V> unmarshal(Adapter<K,V> adapter) throws Exception {
        throw new UnsupportedOperationException("Unmarshalling a list into a map is not supported");
      }
    
      @XmlAccessorType(XmlAccessType.FIELD)
      @XmlType(name="Adapter", namespace="MapAdapter")
      public static final class Adapter<K,V>{
    
        List<MapElement<K,V>> item;
    
        public Adapter(){}
    
        public Adapter(Map<K,V> map){
          item = new ArrayList<MapElement<K,V>>(map.size());
          for (Map.Entry<K, V> entry : map.entrySet()) {
            item.add(new MapElement<K,V>(entry));
          }      
        }
      }
    
      @XmlAccessorType(XmlAccessType.FIELD)
      @XmlType(name="MapElement", namespace="MapAdapter")
      public static final class MapElement<K,V>{
    
        @XmlAnyElement
        private K key;
    
        @XmlAnyElement
        private V value; 
    
        public MapElement(){};
    
        public MapElement(K key, V value){
          this.key = key;
          this.value = value;
        }
    
        public MapElement(Map.Entry<K, V> entry){
          key = entry.getKey();
          value = entry.getValue();
        }
    
        public K getKey() {
          return key;
        }
    
        public void setKey(K key) {
          this.key = key;
        }
    
        public V getValue() {
          return value;
        }
    
        public void setValue(V value) {
          this.value = value;
        }
    
    
      }
    
    }