1

I know there are similar questions around such as How to marshal/unmarshal a Map<Integer, List<Integer>>? and JAXB java.util.Map binding. Also I read Blaise Doughan's blog a lot especially this post: http://blog.bdoughan.com/2013/03/jaxb-and-javautilmap.html and tried to follow what he suggested as much as I can, however I still cannot unmarshal the json payload successfully and really appreciate your help.

The json payload to unmarshal looks like this:

{
    "uri":"\\foo\\dosomthing",
    "action":"POST",
    "queryParameters":[
        "$filter=aaa",
        "$orderby=bbb"
    ],
    "requestData":{
        "data1":{
            "key1":"value1",
            "key2":"value2"
        },
        "ids":[
            "1234",
            "0294"
        ]
    }
}

And I am having problem to unmarshal the "data" into the java.util.Map. The "data" field does not have specific schema so it can contains an array, key-value pairs or any other valid json data. I decided to use a Map to wrap it. Based on what I researched, I think I need XmlAdapter to convert the data properly.

Here are my code:

The Java Schema Class:

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class CustomerRequest
{
    public CustomerRequest() {}

    public CustomerRequest(String uri, String action, List<String>
 queryParameters, Map<String, Object> reqeustData)
    {
        this.uri = uri;
        this.action = action;
        this.queryParameters = queryParameters;
        this.requestData = reqeustData;
    }

    public String getUri()
    {
        return uri;
    }

    public String getAction()
    {
        return action;
    }

    public List<String> getQueryParameters()
    {
        return Collections.unmodifiableList(queryParameters);
    }

    public Map<String, Object> getRequestData()
    {
        return Collections.unmodifiableMap(requestData);
    }

    @XmlElement
    private String uri;

    @XmlElement
    private String action;

    @XmlElementWrapper
    private List<String> queryParameters = new ArrayList<String>();

    @XmlPath(".")
    @XmlJavaTypeAdapter(StringObjectMapAdapter.class)
    private Map<String, Object> requestData = new HashMap<String, Object>();

}

The XmlAdpater:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.adapters.XmlAdapter;

import org.eclipse.persistence.oxm.annotations.XmlVariableNode;

public class StringObjectMapAdapter extends 
XmlAdapter<StringObjectMapAdapter.AdaptedMap, Map<String, Object>> 
{

public static class AdaptedEntry
{
    @XmlTransient
    public String key;

    @XmlValue
    public Object value = new Object();
}
public static class AdaptedMap
{
    @XmlVariableNode("key")
    List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}

@Override
public AdaptedMap marshal(Map<String, Object> map) throws Exception 
{
    AdaptedMap adaptedMap = new AdaptedMap();

    for (Entry<String, Object> entry : map.entrySet())
    {
        AdaptedEntry adaptedEntry = new AdaptedEntry();
        adaptedEntry.key = entry.getKey();
        adaptedEntry.value = entry.getValue();
        adaptedMap.entries.add(adaptedEntry);
    }
    return adaptedMap;
}

@Override
public Map<String, Object> unmarshal(AdaptedMap adaptedMap) throws Exception 
{
    List<AdaptedEntry> adapatedEntries = adaptedMap.entries;
    Map<String, Object> map = new HashMap<String, Object>(adapatedEntries.size());

    for (AdaptedEntry adaptedEntry : adapatedEntries )
    {
        map.put(adaptedEntry.key, adaptedEntry.value);
    }

    return map;
}

}

and finally is my test app:

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;

import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.testng.annotations.Test;

public class TestStringObjectMapAdapter {

    @Test
    public void testUnmarshalFromJson() throws Exception 
    {
        JAXBContext jc = JAXBContext.newInstance(CustomerRequest.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        unmarshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
        unmarshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, false);
        StreamSource json = new StreamSource("test-data.json");
        CustomerRequest request= unmarshaller.unmarshal(json,   
            CustomerRequest.class).getValue();

        assert(request.getUri().equals("\\foo\\dosomthing"));
        assert(request.getAction().equals("POST"));
    }
}

Then when test app runs, an java.lang.ClassCastException exception is generated:

FAILED: testUnmarshalFromJson
java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.DocumentImpl cannot be cast to org.w3c.dom.Element
    at org.eclipse.persistence.internal.oxm.XMLCompositeObjectMappingNodeValue.endSelfNodeValue(XMLCompositeObjectMappingNodeValue.java:468)
    at org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endDocument(UnmarshalRecordImpl.java:606)
    at org.eclipse.persistence.internal.oxm.record.UnmarshalRecordImpl.endElement(UnmarshalRecordImpl.java:1084)
    at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parse(JSONReader.java:304)
    at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parseRoot(JSONReader.java:179)
    at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parse(JSONReader.java:125)
    at org.eclipse.persistence.internal.oxm.record.json.JSONReader.parse(JSONReader.java:140)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:857)
    at org.eclipse.persistence.internal.oxm.record.SAXUnmarshaller.unmarshal(SAXUnmarshaller.java:707)
    at org.eclipse.persistence.oxm.XMLUnmarshaller.unmarshal(XMLUnmarshaller.java:655)
    at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:301)
    at com.absolute.asb.urp.services.domain.TestStringObjectMapAdapter.testUnmarshalFromJson(TestStringObjectMapAdapter.java:21)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
Community
  • 1
  • 1
bunker
  • 125
  • 2
  • 8
  • OK I switched to Jackson and it worked perfectly. `@XmlJavaTypeAdapter` and `@XmlElementWrapper` are not even needed to unmarshal the List and Map. – bunker Dec 10 '14 at 17:35

1 Answers1

1

Maybe you should try creating the correct MoXY JAXBContext like:

    private static synchronized JAXBContext createJAXBContext() throws JAXBException {
        if(jc == null){
            jc = org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(new Class[] {CustomerReqeust.class}, null);
        }
        return jc;
    }

Or use another way like mentioned in http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html

Btw "CustomerReqeust" is a little bit wrong spelled :-)

  • Thanks Sebastian and those typos are fixed:) I tried your suggestion but got the same exception. I think the JAXBContext is created correctly in my code. If I removed the "Map" part, it can unmarshal the string key-value pairs and the list. It will only throw the exception when I try to unmarshal json to the Map value. – bunker Dec 02 '14 at 20:15
  • I am also a bit stuck in a similar issue during the `unmarshalling`. I am trying to `unmarshal` unknown elements into `Map` but it's not working as expected. I have posted the complete question in the below-provided link. If you get a chance can you please have a look at it and provide your suggestion: https://stackoverflow.com/questions/67648941/jaxb-moxy-unmarshalling-assigns-all-field-values-to-mapstring-object-rather-th – BATMAN_2008 May 23 '21 at 18:34