JAXB HashMap unmappable - java

I want to convert a HashMap in a POJO class to XML. I tried using the XmlAdapter but it results in only the key and value pairs of the HashMap being the attributes of the XML Elements. I need the Key to be the Element itself and the value of the HashMap to be the value of the element. For instance, I need the following XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<cart>
<supervisor_id>555</supervisor_id>
<payments>
<payment sequence="1">
<amount>123.45</amount>
<billing_method>12345</billing_method>
<form>card</form>
<delivery_mode>Q</delivery_mode>
</payment>
<payment sequence="2">
<amount>123.45</amount>
<person_id>2333</person_id>
<form>cash</form>
<delivery_mode>Q</delivery_mode>
</payment>
</payments>
</cart>
I created the following classes: MyMapType holds a list of MyMapEntryType class which has two fields namely Key and Value. How do I change the Key element to be #XmlElement and assign the value field to the Key field?
Here are my source files.
MyMapType.java
import java.util.ArrayList;
import java.util.List;
public class MyMapType {
private List<MyMapEntryType> entry = new ArrayList<MyMapEntryType>();
public List<MyMapEntryType> getEntry() {
return entry;
}
public void setEntry(List<MyMapEntryType> entry) {
this.entry = entry;
}
}
MyMapEntryType.java
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlValue;
#XmlAccessorType(XmlAccessType.FIELD)
public class MyMapEntryType {
#XmlAttribute
private String key;
#XmlValue
private String value;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Please also find the adapter class:
MyMapAdapter.java
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class MyMapAdapter extends XmlAdapter<MyMapType, Map<String, String>> {
#Override
public MyMapType marshal(Map<String, String> map) throws Exception {
MyMapType myMapType = new MyMapType();
for(Entry<String, String> entry : map.entrySet()) {
MyMapEntryType myMapEntryType = new MyMapEntryType();
myMapEntryType.setKey(entry.getKey());
myMapEntryType.setValue(entry.getValue());
myMapType.getEntry().add(myMapEntryType);
}
return myMapType;
}
#Override
public Map<String, String> unmarshal(MyMapType map) throws Exception {
HashMap<String, String> hashMap = new HashMap<String, String>();
for(MyMapEntryType myEntryType : map.getEntry()) {
hashMap.put(myEntryType.getKey(), myEntryType.getValue());
}
return hashMap;
}
}
This is the class which has the HashMap field:
XmlElementMap.java
#XmlAccessorType(XmlAccessType.FIELD)
public class XmlElementMap {
#XmlAttribute(name="sequence")
private int sequence;
#XmlJavaTypeAdapter(MyMapAdapter.class)
private Map<String, String> map = new HashMap<String, String>();
public int getSequence() {
return sequence;
}
public void setSequence(int sequence) {
this.sequence = sequence;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
Please advise on how to achieve this.
Regards,
-Anand
Currently it produces the following output:

I have the same requirement "I need the Key to be the Element itself and the value of the HashMap to be the value of the element".
I didn't use customized adapter, but implemented it by converting the HashMap entries dynamically to a list of JAXBElement objects, and then annotated the list with #XmlAnyElement.
#XmlRootElement(name="root")
public class MyMapType {
#XmlAnyElement
public List<JAXBElement> entries = new ArrayList<JAXBElement>();
public MyMapType() { // JAXB required
}
public MyMapType(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
entries.add(new JAXBElement(new QName(entry.getKey()),
String.class, entry.getValue()));
}
}
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(MyMapType.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
MyMapType mt = new MyMapType(map);
marshaller.marshal(mt, System.out);
}
}
The output is,
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<key1>value1</key1>
<key2>value2</key2>
</root>

Note: a marshal/unmarshal example for Map instance can be found here: Dynamic tag names with JAXB.

Related

Parsing complicated JSON request in Spring

I'm developing a RESTful service using Java and Spring-Boot and ran into a problem. I'm getting a put request with request body like:
{
"key":{
"par1":"val1",
"par2":"val2"
},
"data":{
"par1":"val1",
"par2":"val"
}
}
For parsing it I need to create own #RequestBody type. This is it:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.JSONWrappedObject;
import java.io.Serializable;
import java.util.Map;
public class UpdateInfo {
private Map<String, Object> mapKey;
private Map<String, Object> mapData;
ObjectMapper mapper;
public void setMapKey(JsonNode key) {
this.mapKey = mapper.convertValue(key, new TypeReference<Map<String, Object>>(){});
}
public void setMapData(JsonNode) {
this.mapData = mapper.convertValue(data, new TypeReference<Map<String, Object>>(){});
}
public Map<String, Object> getKeys() {
return mapKey;
}
public Map<String, Object> getData() {
return mapData;
}
}
Logically it must work, but I am getting an error:
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: null; nested exception is com.fasterxml.jackson.databind.JsonMappingException: N/A
at [Source: (PushbackInputStream); line: 6, column: 3] (through reference chain: com.sas.rus.spm.UpdateInfo["key"])]
Really can't get the reason, hope for your help
Simply try:
public class UpdateInfo {
private Map<String, Object> key;
private Map<String, Object> data;
//getter and setter
public Map<String, Object> getKey(){
return key;
}
public void setKey(Map<String,Object> key){
this.key = key
}
public Map<String, Object> getData(){
return data;
}
public void setData(Map<String,Object> data){
this.data = data
}
}
JSON parse error: null it is just because you din't initialize mapKey and mapData and getter method is returning null.
Also change the getKeys() method to getKey() as in json your json key is key.
Your class will look like
public class UpdateInfo {
private Map<String, Object> mapKey = new HashMap<>();
private Map<String, Object> mapData = new HashMap<>();
ObjectMapper mapper;
public void setMapKey(JsonNode key) {
this.mapKey = mapper.convertValue(key, new TypeReference<Map<String, Object>>() {
});
}
public void setMapData(JsonNode data) {
this.mapData = mapper.convertValue(data, new TypeReference<Map<String, Object>>() {
});
}
public Map<String, Object> getKey() {
return mapKey;
}
public Map<String, Object> getData() {
return mapData;
}
}

Map<String, MyObject> in jaxb

I got a simple object defined as follows:
#XmlRootElement(name="container")
public class Container{
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String, MyObject> myobject;
I am trying to deserialize/serialize it correctly using jaxb.
MyObject is a simple bean with two attributes "street" and "address" as String.
In stackoverflow I found examples on how to use jaxb with Map but in this case I want to use object MyObject.
MapAdapter is defined as
class MapElements {
#XmlAttribute
public String key;
#XmlElement
public MyObject value;
private MapElements() {
} //Required by JAXB
public MapElements(String key, MyObject value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public MyObject getValue() {
return value;
}
public void setValue(MyObject value) {
this.value = value;
}
}
public class MapAdapter extends XmlAdapter<MapElements[], Map<String, MyObject>> {
public MapAdapter() {
}
public MapElements[] marshal(Map<String, MyObject> arg0) throws Exception {
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry<String, MyObject> entry : arg0.entrySet()){
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());
}
return mapElements;
}
public Map<String, MyObject> unmarshal(MapElements[] arg0) throws Exception {
Map<String, MyObject> r = new TreeMap<String, MyObject>();
for (MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
but once I try to deserialize the object I got error
487:Can not set java.lang.String field com.company.mypackage.myservice.MapElements.key to [Lcom.company.mypackage.myservice.MapElements;
probably it is not possible to do in jaxb because it is strongly typed.
Thanks
I just remembered that I needed a XMLAdapter when I wanted to un/marshall a Map. But... it seems that this is not required for every server / JAXB implementation and sometimes this is even counterproductive. While the error message was not at all helpful to me, as soon as I removed the #XmlJavaTypeAdapter from the map it started working and it marshalled it as expected. So while this isn't really the answer to solve that message, it at least may help others that start with "I need a XMLJavaTypeAdapter for a Map" in mind, as I did.
So summarized: for some JAXB implementation you do not need the #XmlJavaTypeAdapter-annotation, nor do you need that MapAdapter or MapElements-class. It will just work out of the box.

How to assert Map contains Map with entry

I have a unit test that needs to check for a nested map value. I can get my assertion to work by pulling out the entry and matching the underlying Map, but I was looking for a clear way to show what the assertion is doing. Here is a very simplified test:
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasEntry;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class MapContainsMapTest {
#Test
public void testMapHasMap() {
Map<String, Object> outerMap = new HashMap<String, Object>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
// works but murky
assertThat((Map<String, Object>) outerMap.get("nested"), hasEntry("foo", "bar"));
// fails but clear
assertThat(outerMap, hasEntry("nested", hasEntry("foo", "bar")));
}
}
It seems the problem is the outer map is being compared using hasEntry(K key, V value) while what I want to use is hasEntry(Matcher<? super K> keyMatcher, Matcher<? super V> valueMatcher). I am not sure how to coerce the assertion to use the second form.
Thanks in advance.
If you only want to put Map<String, Object> as values in your outerMap adjust the declaration accordingly. Then you can do
#Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
Object value = "bar";
assertThat(outerMap, hasEntry(equalTo("nested"), hasEntry("foo", value)));
}
Object value = "bar"; is necessary for compile reasons. Alternatively you could use
assertThat(outerMap,
hasEntry(equalTo("nested"), Matchers.<String, Object> hasEntry("foo", "bar")));
If You declare outerMap as Map<String, Map<String, Object>> you don't need the ugly cast. Like this:
public class MapContainsMapTest {
#Test
public void testMapHasMap() {
Map<String, Map<String, Object>> outerMap = new HashMap<>();
Map<String, Object> nestedMap = new HashMap<>();
nestedMap.put("foo", "bar");
outerMap.put("nested", nestedMap);
assertThat(outerMap.get("nested"), hasEntry("foo", "bar"));
}
}
I would probably extend a new Matcher for that, something like that (beware, NPEs lurking):
class SubMapMatcher extends BaseMatcher<Map<?,?>> {
private Object key;
private Object subMapKey;
private Object subMapValue;
public SubMapMatcher(Object key, Object subMapKey, Object subMapValue) {
super();
this.key = key;
this.subMapKey = subMapKey;
this.subMapValue = subMapValue;
}
#Override
public boolean matches(Object item) {
Map<?,?> map = (Map<?,?>)item;
if (!map.containsKey(key)) {
return false;
}
Object o = map.get(key);
if (!(o instanceof Map<?,?>)) {
return false;
}
Map<?,?> subMap = (Map<?,?>)o;
return subMap.containsKey(subMapKey) && subMap.get(subMapKey).equals(subMapValue);
}
#Override
public void describeTo(Description description) {
description.appendText(String.format("contains %s -> %s : %s", key, subMapKey, subMapValue));
}
public static SubMapMatcher containsSubMapWithKeyValue(String key, String subMapKey, String subMapValue) {
return new SubMapMatcher(key, subMapKey, subMapValue);
}
}
Try like this :
assertThat(nestedMap).contains(Map.entry("foo", "bar"));
assertThat(outerMap).contains(Map.entry("nested", nestedMap));

How to marshal Map into {key: value, key: value, ...} with MOXy

using Eclipselink MOXy, I have the following class:
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlType(name = "")
public class MyObject {
private Map<String, String> meta;
#XmlPath(".")
#XmlJavaTypeAdapter(MetaMapAdapter.class)
public Map<String, String> getMeta() {
return meta;
}
public setMeta(Map<String, String> m) {
meta = m;
}
}
My AdaptedMap looks like this (credits to JAXB: how to marshall map into <key>value</key>):
import javax.xml.bind.annotation.XmlAnyElement;
public class AdaptedMap {
private Object value;
public AdaptedMap() {}
#XmlAnyElement
public Object getValue() { return value; }
public void setValue(final Object value) { this.value = value; }
}
And the MapAdapter looks like this (credits to JAXB: how to marshall map into <key>value</key>):
import java.util.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.eclipse.persistence.oxm.XMLRoot;
import org.w3c.dom.*;
public class MetaMapAdapter extends XmlAdapter<AdaptedMap, Map<String, String>> {
public MapAdapter() {}
#Override public AdaptedMap marshal(final Map<String, String> map) throws Exception {
if (map == null) { return null; }
final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
final Document document = db.newDocument();
final Element rootElement = document.createElement(getTagName());
document.appendChild(rootElement);
for (final Entry<String, String> entry : map.entrySet()) {
final Element mapElement = document.createElement(entry.getKey());
mapElement.setTextContent(entry.getValue());
rootElement.appendChild(mapElement);
}
final AdaptedMap adaptedMap = new AdaptedMap();
adaptedMap.setValue(document);
return adaptedMap;
}
#Override public Map<String, String> unmarshal(final AdaptedMap adaptedMap) {
if (adaptedMap == null) { return null; }
final Map<String, String> map = new HashMap<String, String>();
final Element rootElement = (Element) adaptedMap.getValue();
final NodeList childNodes = rootElement.getChildNodes();
for (int x = 0, size = childNodes.getLength(); x < size; x++) {
final Node childNode = childNodes.item(x);
if (childNode.getNodeType() == Node.ELEMENT_NODE) {
map.put(childNode.getLocalName(), childNode.getTextContent());
}
}
return map;
}
}
By using Eclipselink MOXy, I'm able to get this JSON in return with the help of XmlPath:
{
"meta": {
"akey":"avalue",
"bkey":"bvalue"
}
}
Unfortunately, I'm unable to unmarshal to MyObject in reverse due to the usage of XmlPath to collapse the outer meta element.
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 :(
Anyone knows how I can resolve this?
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

Bind multiple elements to map with attribute as key with java XML annotations, JAXB

I have an XML source from which I unmarshall Objects with JAXB.
The XML source:
<album>
<name>something</name>
<id>003030</id>
<artist>someone</artist>
...
</album>
The java source is like (with the required getter/setters as well):
#XmlRootElement(name="album")
class Album {
String name;
Long id;
String artist;
...
}
So far so good. Now I get some image urls in different sizes within album list:
...
<image size="small">http://.../small.jpg</image>
<image size="medium">http://.../medium.jpg</image>
<image size="large">http://.../large.jpg</image>
...
I want to map it to a java Map something like this:
Map<String,String> imageUrls;
Where the map's key would be the size attribute and the map's value would be the element value.
If it's possible, how should I annotate this variable?
helper class Pair
#XmlAccessorType(XmlAccessType.FIELD)
public class Pair {
#XmlAttribute
private String key;
#XmlValue
private String value;
public Pair() {
}
public Pair(String key, String value) {
this.key = key;
this.value = value;
}
//... getters, setters
}
List of pairs
#XmlAccessorType(XmlAccessType.FIELD)
public class PairList
{
private List<Pair> values = new ArrayList<Pair>();
public PairList() {
}
//...
}
adaptor
public class MapAdaptor extends XmlAdapter<PairList, Map<String, String>>
{
#Override
public Map<String, String> unmarshal(PairList list) throws Exception
{
Map<String, String> retVal = new HashMap<String, String>();
for (Pair keyValue : list.getValues())
{
retVal.put(keyValue.getKey(), keyValue.getValue());
}
return retVal;
}
#Override
public PairList marshal(Map<String, String> map) throws Exception
{
PairList retVal = new PairList();
for (String key : map.keySet())
{
retVal.getValues().add(new Pair(key, map.get(key)));
}
return retVal;
}
}
usage in your entity
#XmlJavaTypeAdapter(value = MapAdaptor.class)
private Map<String, String> imageUrls = new HashMap<String, String>();
PS
You can do it without class PairList using Pair[] instead of PairList
adaptor
public class MapAdaptor extends XmlAdapter<Pair[], Map<String, String>>
{
#Override
public Map<String, String> unmarshal(Pair[] list) throws Exception
{
Map<String, String> retVal = new HashMap<String, String>();
for (Pair keyValue : Arrays.asList(list))
{
retVal.put(keyValue.getKey(), keyValue.getValue());
}
return retVal;
}
#Override
public Pair[] marshal(Map<String, String> map) throws Exception
{
List<Pair> retVal = new ArrayList<Pair>();
for (String key : map.keySet())
{
retVal.add(new Pair(key, map.get(key)));
}
return retVal.toArray(new Pair[]{});
}
}
but in this case you can't control name of every pair. It will be item and you can't change it
<item key="key2">valu2</item>
<item key="key1">valu1</item>
PS2
If you will try use List<Pair> instead of PairList, you will get Exception
ERROR: java.util.List haven't no-arg constructor

Categories

Resources