JAXB: how to marshall map into value

There may be a valid reason why you want to do this, but generating this kind of XML is generally best avoided. Why? Because it means that the XML elements of your map are dependent on the runtime contents of your map. And since XML is usually used as an external interface or interface layer this is not desirable. Let me explain.

The Xml Schema (xsd) defines the interface contract of your XML documents. In addition to being able to generate code from the XSD, JAXB can also generate the XML schema for you from the code. This allows you to restrict the data exchanged over the interface to the pre-agreed structures defined in the XSD.

In the default case for a Map<String, String>, the generated XSD will restrict the map element to contain multiple entry elements each of which must contain one xs:string key and one xs:string value. That’s a pretty clear interface contract.

What you describe is that you want the xml map to contain elements whose name will be determined by the content of the map at runtime. Then the generated XSD can only specify that the map must contain a list of elements whose type is unknown at compile time. This is something that you should generally avoid when defining an interface contract.

To achieve a strict contract in this case, you should use an enumerated type as the key of the map instead of a String. E.g.

public enum KeyType {
 KEY, KEY2;
}

@XmlJavaTypeAdapter(MapAdapter.class)
Map<KeyType , String> mapProperty;

That way the keys which you want to become elements in XML are known at compile time so JAXB should be able to generate a schema that would restrict the elements of map to elements using one of the predefined keys KEY or KEY2.

On the other hand, if you wish to simplify the default generated structure

<map>
    <entry>
        <key>KEY</key>
        <value>VALUE</value>
    </entry>
    <entry>
        <key>KEY2</key>
        <value>VALUE2</value>
    </entry>
</map>

To something simpler like this

<map>
    <item key="KEY" value="VALUE"/>
    <item key="KEY2" value="VALUE2"/>
</map>

You can use a MapAdapter that converts the Map to an array of MapElements as follows:

class MapElements {
    @XmlAttribute
    public String key;
    @XmlAttribute
    public String value;

    private MapElements() {
    } //Required by JAXB

    public MapElements(String key, String value) {
        this.key = key;
        this.value = value;
    }
}


public class MapAdapter extends XmlAdapter<MapElements[], Map<String, String>> {
    public MapAdapter() {
    }

    public MapElements[] marshal(Map<String, String> arg0) throws Exception {
        MapElements[] mapElements = new MapElements[arg0.size()];
        int i = 0;
        for (Map.Entry<String, String> entry : arg0.entrySet())
            mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());

        return mapElements;
    }

    public Map<String, String> unmarshal(MapElements[] arg0) throws Exception {
        Map<String, String> r = new TreeMap<String, String>();
        for (MapElements mapelement : arg0)
            r.put(mapelement.key, mapelement.value);
        return r;
    }
}

Leave a Comment