JAXB: how to marshall map into key value /key
the code provided didn't work for me.
I found another way to Map :
MapElements :
package com.cellfish.mediadb.rest.lucene;
import javax.xml.bind.annotation.XmlElement;
class MapElements
{
@XmlElement public String key;
@XmlElement public Integer value;
private MapElements() {} //Required by JAXB
public MapElements(String key, Integer value)
{
this.key = key;
this.value = value;
}
}
MapAdapter :
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.adapters.XmlAdapter;
class MapAdapter extends XmlAdapter<MapElements[], Map<String, Integer>> {
public MapElements[] marshal(Map<String, Integer> arg0) throws Exception {
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry<String, Integer> entry : arg0.entrySet())
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());
return mapElements;
}
public Map<String, Integer> unmarshal(MapElements[] arg0) throws Exception {
Map<String, Integer> r = new HashMap<String, Integer>();
for (MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
The rootElement :
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement
public class Root {
private Map<String, Integer> mapProperty;
public Root() {
mapProperty = new HashMap<String, Integer>();
}
@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, Integer> getMapProperty() {
return mapProperty;
}
public void setMapProperty(Map<String, Integer> map) {
this.mapProperty = map;
}
}
I found the code in this website :
http://www.developpez.net/forums/d972324/java/general-java/xml/hashmap-jaxb/
JAXB Marshal and Unmarshal Map to/from key value /key
A very small correction:
map.put(element.getNodeName(), element.getTextContent());
JAXB java.util.Map to key value pairs
Like I hinted in my comment, this cannot be achieved with JAXB alone. In the JAXB specification (JSR 222) it says:
In all application scenarios, we create a Java object-level binding of the schema.
That means that the scope of the binding is the same as the scope of the schema, which is static. A JAXB binding is not meant to be changed without recompiling the code. There are some exceptions, e.g. for xs:anyAttribute
which is discussed in section 6.9 of the specification, but since you didn't vote for the answer suggesting the use of @XmlAnyAttribute
you probably don't want to live with the limitations - e.g. only have QName
keys in your map.
I hope you are convinced that to do what you want with JAXB is a really bad idea, but just for reference below is an example that modifies the document after marshalling to bring it to the structure you want. You can copy and paste it into a single file and compile it with Java 7. The output will look like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<mapExample>
<map France="Paris" Japan="Tokyo"/>
</mapExample>
My code only shows the marshalilng the other direction is equivalent:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@XmlRootElement
class MapExample {
@XmlJavaTypeAdapter(MapXmlAdapter.class)
@XmlElement(name="map")
private Map<String, String> data = new HashMap<>();
public static void main(String[] args) throws Exception {
MapExample example = new MapExample();
example.data.put("France", "Paris");
example.data.put("Japan", "Tokyo");
JAXBContext context = JAXBContext.newInstance(MapExample.class);
Marshaller marshaller = context.createMarshaller();
DOMResult result = new DOMResult();
marshaller.marshal(example, result);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
Document document = (Document)result.getNode();
XPathExpression expression = xpath.compile("//map/entry");
NodeList nodes = (NodeList)expression.evaluate(document, XPathConstants.NODESET);
expression = xpath.compile("//map");
Node oldMap = (Node)expression.evaluate(document, XPathConstants.NODE);
Element newMap = document.createElement("map");
for (int index = 0; index < nodes.getLength(); index++) {
Element element = (Element)nodes.item(index);
newMap.setAttribute(element.getAttribute("key"),
element.getAttribute("value"));
}
expression = xpath.compile("//map/..");
Node parent = (Node)expression.evaluate(document, XPathConstants.NODE);
parent.replaceChild(newMap, oldMap);
TransformerFactory.newInstance().newTransformer().
transform(new DOMSource(document), new StreamResult(System.out));
}
}
class MapXmlAdapter extends XmlAdapter<MyMap, Map<String, String>> {
@Override
public Map<String, String> unmarshal(MyMap value) throws Exception {
throw new UnsupportedOperationException();
}
@Override
public MyMap marshal(Map<String, String> value) throws Exception {
MyMap map = new MyMap();
map.entries = new ArrayList<MyEntry>();
for (String key : value.keySet()) {
MyEntry entry = new MyEntry();
entry.key = key;
entry.value = value.get(key);
map.entries.add(entry);
}
return map;
}
}
class MyMap {
@XmlElement(name="entry")
public List<MyEntry> entries;
}
class MyEntry {
@XmlAttribute
public String key;
@XmlAttribute
public String value;
}
Using JAXB to create XML from MAP
I did it! used this Example
From that above post: Created this class:
public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>>{
@Override
public Map<String, String> unmarshal(MapWrapper v) throws Exception {
Map<String, String> map = new HashMap<String,String>();//v.toMap();
return map;
}
@Override
public MapWrapper marshal(Map<String, String> m) throws Exception {
MapWrapper wrapper = new MapWrapper();
for(Map.Entry<String, String> entry : m.entrySet()){
wrapper.addEntry(new JAXBElement<String>(new QName(entry.getKey()), String.class, entry.getValue()));
}
return wrapper;
}
}
MapWrapper Class:
@XmlType
public class MapWrapper{
private List<JAXBElement<String>> properties = new ArrayList<>();
public MapWrapper(){
}
@XmlAnyElement
public List<JAXBElement<String>> getProperties() {
return properties;
}
public void setProperties(List<JAXBElement<String>> properties) {
this.properties = properties;
}
public void addEntry(JAXBElement<String> prop){
properties.add(prop);
}
public void addEntry(String key, String value){
JAXBElement<String> prop = new JAXBElement<String>(new QName(key), String.class, value);
addEntry(prop);
}
}
Created This CustomMap
@XmlRootElement(name="RootTag")
public class CustomMap extends MapWrapper{
public CustomMap(){
}
}
Test Code by creating XML:
private static void writeAsXml(Object o, Writer writer) throws Exception
{
JAXBContext jaxb = JAXBContext.newInstance(o.getClass());
Marshaller xmlConverter = jaxb.createMarshaller();
xmlConverter.setProperty("jaxb.formatted.output", true);
xmlConverter.marshal(o, writer);
}
CustomMap map = new CustomMap();
map.addEntry("Key1","Value1");
map.addEntry("Key2","Value2");
map.addEntry("Key3","Value3");
map.addEntry("Key4","Value4");
writeAsXml(map, new PrintWriter(System.out));
And produced XML:
<RootTag>
<Key1>Value1</Key1>
<Key2>Value2</Key2>
<Key3>Value3</Key3>
<Key4>Value4</Key4>
</RootTag>
I needed only Marshaling So did not implement that Unmarshal part.
How to marshall Map String, List Objects using JAXB
Maps containing collections as values are a bit tricky; you need an adapter and classes for representing the map entries in a way JAXB can handle.
@XmlAccessorType(XmlAccessType.FIELD)
public class ListOfEntry {
@XmlElement
private List<Entry> list = new ArrayList<>();
public List<Entry> getList(){ return list; }
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Entry {
@XmlElement
private String key;
@XmlElement
private List<Parameter> list = new ArrayList<>();
public String getKey(){ return key; }
public void setKey( String value ){ key = value; }
public List<Parameter> getList(){ return list; }
}
The Adapter does the transformations back and forth:
public class Adapter
extends XmlAdapter<ListOfEntry, Map<String, List<Parameter>>> {
@Override
public Map<String, List<Parameter>> unmarshal(ListOfEntry loe)
throws Exception {
Map<String, List<Parameter>> map = new HashMap<>();
for(Entry entry : loe.getList() ) {
map.put(entry.getKey(), entry.getList() );
}
return map;
}
@Override
public ListOfEntry marshal(Map<String, List<Parameter>> map)
throws Exception {
ListOfEntry loe = new ListOfEntry();
for(Map.Entry<String, List<Parameter>> mapEntry : map.entrySet()) {
Entry entry = new Entry();
entry.setKey( mapEntry.getKey() );
entry.getList().addAll( mapEntry.getValue() );
loe.getList().add(entry);
}
return loe;
}
}
And you must add an annotation to the Map field:
@XmlElement
@XmlJavaTypeAdapter(Adapter.class)
private Map<String, List<Parameter>> parameterMap = new HashMap<>();
JAXB unmarshal elements of XML to Map
You could do the following:
XmlAdapter (MapAdapter
)
You could do the following for your XmlAdapter
where you convert an instance of Map
to an object that has a List
of DOM Element
. You will construct the instances of Element
so that the name is they key from the map entry, and the text content is the value.
import java.util.*;
import java.util.Map.Entry;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.*;
public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, String>> {
private DocumentBuilder documentBuilder;
public MapAdapter() throws Exception {
documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
}
public static class AdaptedMap {
@XmlAnyElement
public List<Element> elements = new ArrayList<Element>();
}
@Override
public AdaptedMap marshal(Map<String, String> map) throws Exception {
Document document = documentBuilder.newDocument();
AdaptedMap adaptedMap = new AdaptedMap();
for(Entry<String, String> entry : map.entrySet()) {
Element element = document.createElement(entry.getKey());
element.setTextContent(entry.getValue());
adaptedMap.elements.add(element);
}
return adaptedMap;
}
@Override
public Map<String, String> unmarshal(AdaptedMap adaptedMap) throws Exception {
HashMap<String, String> map = new HashMap<String, String>();
for(Element element : adaptedMap.elements) {
map.put(element.getLocalName(), element.getTextContent());
}
return map;
}
}
Optimizing the Use of MapAdapter
To improve performance we want to minimize the number of times the DocumentBuiderFactory
and DocumentBuilder
is instantiated. We can do this by creating an instance of the MapAdapter
for JAXB to use and set it on the Marshaller
and Unmarshaller
. This way JAXB will use that instance instead of creating a new one each time the adapter is required.
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(SMSDetail.class);
MapAdapter mapAdapter = new MapAdapter();
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setAdapter(mapAdapter);
File xml = new File("src/forum27182975/input.xml");
SMSDetail smsDetail = (SMSDetail) unmarshaller.unmarshal(xml);
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(mapAdapter);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(smsDetail, System.out);
}
}
If You are Using MOXy as your JAXB (JSR-222) Provider
If you are using MOXy as your JAXB provider then you can leverage the @XmlVariableNode
extension to make the mapping of this use case easier:
- http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html
How to marshal Map into {key: value, key: value, ...} with MOXy
On a side note, I'm also not able to use the new XmlVariableNode in
Eclipselink 2.6 as I'm only allowed to use stable releases of the API
:(
@XmlVariableNode
has also been included in EclipseLink 2.5.1 which is now released:
- http://www.eclipse.org/eclipselink/downloads/
This annotation is well suited for mapping your use case:
- http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-using-maps-key-as.html
JaxB How to marshall the content of a list as individual tags
My Advice
My advice is don't do it this way. Instead have your XML message be the following (it will make it easier for everyone to process your XML):
<items>
<item>apple</item>
<item>banana</item>
<item>orange</item>
</items>
How You Could Do It
OK, you have decided not to follow my advice :). Here is how you can do it:
Create an
XmlAdapter
that can convert aString
to an instance oforg.w3c.dom.Element
with its name equal to theString
.import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class StringAdapter extends XmlAdapter<Object, String> {
private Document document;
@Override
public String unmarshal(Object v) throws Exception {
Element element = (Element) v;
return element.getTagName();
}
@Override
public Object marshal(String v) throws Exception {
return getDocument().createElement(v);
}
private Document getDocument() throws Exception {
if(null == document) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
document = db.newDocument();
}
return document;
}
}Annotate your
List<String>
field/property with@XmlJavaTypeAdapter
pointing to yourXmlAdapter
and the@XmlAnyElement
annotation.import java.util.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlRootElement
public class Items {
private List<String> items = new ArrayList<String>();
@XmlAnyElement
@XmlJavaTypeAdapter(StringAdapter.class)
public List<String> getItems() {
return items;
}
}To improve performance, make sure your
XmlAdapter
holds an instance ofDocument
and set theXMLAdapter
on theMarshaller
to make it stateful to avoid the need to recreate it each time theXmlAdapter
is called.import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Items.class);
Items items = new Items();
items.getItems().add("apple");
items.getItems().add("banana");
items.getItems().add("orange");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setAdapter(new StringAdapter());
marshaller.marshal(items, System.out);
}
}
Related Topics
How to Make a Java Thread Wait for Another Thread's Output
Converting Between Java.Time.Localdatetime and Java.Util.Date
How to Get the SQL of a Preparedstatement
How to Compare Two Dates Without the Time Portion
Why Does This Java Method Appear to Have Two Return Types
Adding Elements to a Collection During Iteration
Xml Configuration Versus Annotation Based Configuration
Java Sslhandshakeexception "No Cipher Suites in Common"
How to Convert Outputstream to Inputstream
How to Create a Jandex Index in Quarkus for Classes in a External Module
When Do Java Generics Require <? Extends T> Instead of <T> and Is There Any Downside of Switching
Add Jar Files to a Spark Job - Spark-Submit
How to Restart a Java Application
Concurrentmodificationexception Despite Using Synchronized
Practical Uses for Atomicinteger
What Is the Best Approach Using Jdbc for Parameterizing an in Clause