One of the thing that can be a bit tricky when using JAXB is to map a java.util.Map to XML. In this example we have a provider object. The provider has a name and a map containing properties.
Below is the provider XML document:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<provider> <name>Provider Name</name> <properties> <property> <name>address</name> <value>194.132.9.45</value> </property> <property> <name>username</name> <value>testuser</username> </property> <property> <name>secret</name> <value>433395868950ifkjjrif9985848rufj</value> </property> </properties> </provider> |
And then the Provider class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.tomasmalmsten.example.jaxb; import java.util.Map; public class Provider { private String name; private Map<String, String> properties; void setProperties(Map<String, String> properties) { this.properties = properties; } String name() { return name; } Map<String, String> properties() { return properties; } void setName(String name) { this.name = name; } } |
JAXB sees the properties object in the XML document as a list of property entries. Therefore we need to transform the list into a map. To do this we need three supporting classes. The first one is the class containing the entry, or property:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.tomasmalmsten.example.jaxb; import javax.xml.bind.annotation.XmlElement; class Property { @XmlElement(name = "name") private String name; @XmlElement(name = "value") private String value; Property() { } Property(String name, String value) { this.name = name; this.value = value; } String value() { return value; } String name() { return name; } } |
Next we need the class that contains the list of entries, or the properties:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.tomasmalmsten.example.jaxb; import javax.xml.bind.annotation.XmlElement; import java.util.ArrayList; import java.util.Collections; import java.util.List; class Properties { @XmlElement(name = "property") private List<PropertyEntry> entries = new ArrayList<>(); List<PropertyEntry> entries() { return Collections.unmodifiableList(entries); } void addEntry(PropertyEntry entry) { entries.add(entry); } } |
To transform the list into a map we need an XmlAdapter. An XmlAdapter is a class that instructs JAXB on how to marshal and unmarshal an object that does not conform to the JAXB standard. This could for example be a class without a public no args constructor, or as in our case the provider properties map:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.tomasmalmsten.example.jaxb; import javax.xml.bind.annotation.adapters.XmlAdapter; import java.util.HashMap; import java.util.Map; class PropertyAdapter extends XmlAdapter<Properties, Map<String, String>> { @Override public Map<String, String> unmarshal(Properties in) throws Exception { HashMap<String, String> hashMap = new HashMap<>(); for (PropertyEntry entry : in.entries()) { hashMap.put(entry.name(), entry.value()); } return hashMap; } @Override public Properties marshal(Map<String, String> map) throws Exception { Properties props = new Properties(); for (Map.Entry<String, String> entry : map.entrySet()) { props.addEntry(new PropertyEntry(entry.getKey(), entry.getValue())); } return props; } } |
Now what is left is to tell JAXB to use the XmlAdaptor when handling the properties field. To do this we use the @XmlJavaTypeAdapter annotation. Below is the fully annotated Provider class:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package com.tomasmalmsten.example.jaxb; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.util.Map; @XmlRootElement(name = "provider") @XmlAccessorType(XmlAccessType.FIELD) public class Provider { @XmlElement(name = "name") private String name; @XmlElement(name = "properties") @XmlJavaTypeAdapter(PropertyAdapter.class) private Map<String, String> properties; void setProperties(Map<String, String> properties) { this.properties = properties; } String name() { return name; } Map<String, String> properties() { return properties; } void setName(String name) { this.name = name; } } |
