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;
}
} |
