I have a bean type that needs to be marshalled to and unmarshalled from JSON using the EclipseLink JAXB implementation. When the bean includes a field of type java.util.Map
, the unmarshalling fails with:
Exception in thread "main" javax.xml.bind.UnmarshalException
- with linked exception:
[Exception [EclipseLink-25023] (Eclipse Persistence Services - 2.6.7.v20190604-418f1a1c56): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: No descriptor found while unmarshalling element mapped to attribute value.]
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.handleXMLMarshalException(JAXBUnmarshaller.java:1110)
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:172)
at org.example.jaxb.ExampleBean.main(ExampleBean.java:43)
Caused by: Exception [EclipseLink-25023] (Eclipse Persistence Services - 2.6.7.v20190604-418f1a1c56): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: No descriptor found while unmarshalling element mapped to attribute value.
at org.eclipse.persistence.exceptions.XMLMarshalException.noDescriptorFound(XMLMarshalException.java:284)
at org.eclipse.persistence.internal.oxm.record.deferred.DescriptorNotFoundContentHandler.processComplexElement(DescriptorNotFoundContentHandler.java:35)
at org.eclipse.persistence.internal.oxm.record.deferred.DeferredContentHandler.startElement(DeferredContentHandler.java:94)
I have written a short demonstration of the problem in which I use JAXB to write out a bean and then immediately read back what was produced. It amazes me that JAXB cannot parse its own output! Full code is below:
package org.example.jaxb;
import java.io.*;
import java.util.*;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
@XmlRootElement(name = "ExampleBean")
@XmlAccessorType(XmlAccessType.FIELD)
public class ExampleBean {
@XmlElement
private Map<String, Object> properties;
public synchronized Object getProperty(String key) {
return properties != null ? properties.get(key) : null;
}
public synchronized void setProperty(String key, Object value) {
if (properties == null) {
properties = new HashMap<>();
}
properties.put(key, value);
}
public static void main(String[] args) throws Exception {
ExampleBean initialBean = new ExampleBean();
initialBean.setProperty("Foo", "Bar");
JAXBContext context = JAXBContext.newInstance(ExampleBean.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
marshaller.marshal(initialBean, buffer);
System.out.println(buffer.toString());
Unmarshaller unmarshaller = context.createUnmarshaller();
unmarshaller.setProperty("eclipselink.media-type", "application/json");
ExampleBean loadedBean = (ExampleBean) unmarshaller.unmarshal(new ByteArrayInputStream(buffer.toByteArray()));
System.out.printf("Read back ExampleBean with property %s = %s%n", "Foo", loadedBean.getProperty("Foo")); }
}
The full output is as follows:
{
"ExampleBean" : {
"properties" : {
"entry" : [ {
"key" : "Foo",
"value" : {
"type" : "string",
"value" : "Bar"
}
} ]
}
}
}
Exception in thread "main" javax.xml.bind.UnmarshalException
- with linked exception:
[Exception [EclipseLink-25023] (Eclipse Persistence Services - 2.6.7.v20190604-418f1a1c56): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: No descriptor found while unmarshalling element mapped to attribute value.]
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.handleXMLMarshalException(JAXBUnmarshaller.java:1110)
at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:172)
at org.example.jaxb.ExampleBean.main(ExampleBean.java:43)
Caused by: Exception [EclipseLink-25023] (Eclipse Persistence Services - 2.6.7.v20190604-418f1a1c56): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: No descriptor found while unmarshalling element mapped to attribute value.
at org.eclipse.persistence.exceptions.XMLMarshalException.noDescriptorFound(XMLMarshalException.java:284)
at org.eclipse.persistence.internal.oxm.record.deferred.DescriptorNotFoundContentHandler.processComplexElement(DescriptorNotFoundContentHandler.java:35)
at org.eclipse.persistence.internal.oxm.record.deferred.DeferredContentHandler.startElement(DeferredContentHandler.java:94)
[... stack trace abbreviated]
What am I doing wrong? How can I parse this JSON data to a Map?
Note that I cannot use a solution that changes the shape of JSON output for the map entries, since I need to consume existing files with that shape.
Relevant versions as follows:
- JDK 1.8.0_181
org.eclipse.persistence.moxy/core/asm
2.6.7 (from Maven Central).
Update
At the suggestion of @MilenDyankov I changed the map type to Map<String,String>
. Interestingly this does now work, though unfortunately it doesn't help me because I need to read generic data into the map from existing files. When the map value is String, the value
node of the entry is a plain text field rather than a nested node with type and value field.
Another interesting observation is that the marshal/unmarshal (with the original Map<String,Object>
) work together fine for XML formatted data but not JSON.