2

There are several previous questions around using JaxB to marshall/unmarshall a java.util.Map, many of which get pointed back to this example, which works great:

http://blog.bdoughan.com/2013/03/jaxb-and-javautilmap.html

However, I can't get JaxB to be able to marshall/unmarshall instances of Map if the map is not a member of the @XmlRootElement. For example, here's a root element class,

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class Customer {

    private MyField myField

    MyField getMyField() {
        return myField
    }

    void setMyField(MyField myField) {
        this.myField = myField
    }

}

The definition of it's field class:

@XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{

    Map<String, String> getSomeMap() {
        return someMap
    }

    void setSomeMap(Map<String, String> someMap) {
        this.someMap = someMap
    }

    @XmlElement
    private Map<String, String> someMap = new HashMap<String, String>()
}

And some code to drive the marshalling:

    JAXBContext jc = JAXBContext.newInstance(Customer.class)

    Customer customer = new Customer()
    MyField myField1 = new MyField()
    myField1.someMap.put("foo", "bar")
    myField1.someMap.put("baz", "qux")
    customer.myField =  myField1

    Marshaller marshaller = jc.createMarshaller()
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
    marshaller.marshal(customer, System.out)

This example results in:

java.util.Map is an interface, and JAXB can't handle interfaces.
java.util.Map does not have a no-arg default constructor.

I am writing my code in Groovy rather than Java, but I don't think it should make much of a difference.

Sean
  • 2,315
  • 20
  • 25

3 Answers3

1

I was able to encounter the same behavior using JAXB by creating a TestController of type @RestController, using Spring Boot.

import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(value = "test")
class TestController {

    @RequestMapping(value = "findList")
    List findList() {
        ["Test1", "Test2", "Test3"] as ArrayList<String>
    }

    @RequestMapping(value = "findMap")
    Map findMap() {
        ["T1":"Test1", "T2":"Test2", "T3":"Test3"] as HashMap<String,String>
    }

    @RequestMapping(value = "")
    String find(){
        "Test Something"
    }
}

With JAXB as the default implementation in SpringBoot, I could reproduce the issue that the /test/findList would correctly render XML, but /test/findMap would generate an error as described in the initial posting.

For me, the solution to the problem is to switch the XML rendering library to Jackson (there are others like XStream as well).

Using Gradle for the build file (build.gradle), I simply add the Jackson dependencies, very similar to how you would if using Maven:

'com.fasterxml.jackson.core:jackson-core:2.7.1', 'com.fasterxml.jackson.core:jackson-annotations:2.7.1', 'com.fasterxml.jackson.core:jackson-databind:2.7.1-1', 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.7.1', 'org.codehaus.woodstox:woodstox-core-asl:4.4.1',

pczeus
  • 7,709
  • 4
  • 36
  • 51
0

I have experienced this before myself. Bottom line is that the warning is telling you exactly the problem. You have defined your field as type java.util.Map. JAXB does not support interfaces. To correct your problem, you need to change the declaration of your field to a concrete Map type like:

private HashMap<String, String> someMap = new HashMap<String, String>()

Your other option is described in the link you referenced. You need to have a

MapAdapter class as referenced in the link you provided and then include that in the annotation, hinting to JAXB how it should marshal/unmarshal the Map type.

I think this link gives a clearer example of how to create and implement the MapAdapter:

JAXB: how to marshall map into <key>value</key>

Community
  • 1
  • 1
pczeus
  • 7,709
  • 4
  • 36
  • 51
  • You are incorrect. If you follow the link posted in the original question and follow the blog through, you will see that the `MapAdapter` is only used to change how the map is unmarshalled into XML - but it is *not* originally needed to just achieve unmarshalling/marshalling. This is also the case in the link you posted. Also, you can try the above example using `java.util.List` (also an interface) and have no problems with marshalling/unmarshalling with jaxb. – Sean Feb 09 '16 at 01:56
  • Let me ask you this. If you change the type from Map to HashMap, with no other modifications, does it then work? – pczeus Feb 09 '16 at 20:07
  • After your response, I created a test @RestController using SpringBoot. I was able to reproduce your situation - List renders, Map doesn't. So, you are right about the unusual behavior for Map. I will post my solution to the problem, which may not work for you, as the solution for me was to change the XML rendering from JAXB to Jackson, which worked great for me. – pczeus Feb 10 '16 at 15:33
  • Jackson is a good idea, I will try that. About changing the type to HashMap, that keeps an exception from being thrown, but the contents of the map are not persisted. The resulting xml for the example I posted is: – Sean Feb 10 '16 at 15:47
0

The answer to the specific issue I was having ended up being removing the @XmlElement annotation from the Map field like so:

@XmlAccessorType(XmlAccessType.FIELD)
public static class MyField{

  Map<String, String> getSomeMap() {
      return someMap
  }

  void setSomeMap(Map<String, String> someMap) {
      this.someMap = someMap
  }

  //@XmlElement Remove this annotation
  private Map<String, String> someMap = new HashMap<String, String>()
}  

Without that annotation, the marshalling/unmarshalling works fine, and still interprets the Map as an XmlElement - there seems to be a bug with that annotation specifically. However, as @dlcole points out, an alternative (that would allow you to have more control over the format of your serialized representation) is to use Jackson rather than JAXB.

Sean
  • 2,315
  • 20
  • 25