Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB: how to marshall map into <key>value</key>

Tags:

java

xml

jaxb

jaxb2

The question is about JAXB Map marshalling - there is plenty of examples on how to marhsall a Map into a structure like follows:

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

In fact, this is natively supported by JAXB. What I need, however, is the XML where key is the element name, and value is its content:

<map>   <key> VALUE </key>   <key2> VALUE2 </key2>  ... </map> 

I didn't succeed implementing my Map adapter the way it is recommended by JAXB developers (https://jaxb.dev.java.net/guide/Mapping_your_favorite_class.html), as I need, he - dynamic attribute name :)

Is there any solution for that?

P.S. Currently I have to create a dedicated container class for each typical set of key-value pairs I want to marshall to XML - it works, but I have to create way too many of these helper containers.

like image 345
Timur Avatar asked Oct 15 '10 10:10

Timur


People also ask

How does JAXB marshalling work?

In JAXB, marshalling involves parsing an XML content object tree and writing out an XML document that is an accurate representation of the original XML document, and is valid with respect the source schema. JAXB can marshal XML data to XML documents, SAX content handlers, and DOM nodes.

How do you Unmarshal string JAXB?

To unmarshal an xml string into a JAXB object, you will need to create an Unmarshaller from the JAXBContext, then call the unmarshal() method with a source/reader and the expected root object.

How do I set namespace prefix in JAXB?

If you are using the default JAXB implementation provided with Java 6 or later, you can configure the namespace prefixes by extending the NamespacePrefixMapper and setting a property to tell the marshaller to use your extension.


1 Answers

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;     } } 
like image 116
Justin Rowe Avatar answered Oct 06 '22 03:10

Justin Rowe