I produce JSON with a List<> member inside. It is marshalled OK.
However, my consuming (third-)party complains about a missing []-pair, when the list has only one element. What I produce is like:
"mylist":{"id":104,"name":"Only one found"} // produced
while my consumer expects:
"mylist":[{"id":104,"name":"Only one found"}] // expected by third party
Is my implementation producing incorrect JSON?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
The JAXB (JSR-222) specification does not cover JSON-binding. The behaviour you are seeing is most likely due to a JAXB implementation being used with a library like Jettison. Jettison converts StAX events to/from JSON and can only detect a list when an element occurs more than once (see: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html). EclipseLink JAXB offers native JSON binding and can correctly represent arrays of size 1.
JAVA MODEL
Foo
import java.util.List;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
private List<Bar> mylist;
}
Bar
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Bar {
private int id;
private String name;
}
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
DEMO CODE
Demo
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum15404528/input.json");
Foo foo = unmarshaller.unmarshal(json, Foo.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.json/Output
We see that the mylist is correctly represented as a JSON array.
{
"mylist" : [ {
"id" : 104,
"name" : "Only one found"
} ]
}
ADDITIONAL INFORMATION
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html
Related
I started with a service that consumes and produces output in JSON. I use the resteasy-jackson-provider for (de)marshalling which takes its information from the class description. After a while I was asked to add XML as MediaType. So I annotated my DTOs with JAXB annotations and added the resteasy-jaxb-provider. As a result, I observed that the produced JSON output derives from the JAXB annotations which differs from the original format.
I am on RestEasy Version 3.0.4. As described I use the following providers
resteasy-jackson-provider
resteasy-axb-provider.
resteasy-jettison-provider, because I integrated RestEasy into Spring and this provider is a transitive dependency.
I got aware of the problem when I
used XmlElementWrapper for lists and when
I wrote a custom XmlAdapter which serializes a complex data structure Map<String, List<String>>. Requests with XML MediaType are fine. Requests with JSON MediaType cause an exception. Jackson seems to exploit the XmlAdapter for further information. This was not the case before. Jackson was able to marshall the Map without the JAXB annotations.
org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field "customer" (Class x.y.z.OptionalParametersMapType), not marked as ignorable
at [Source: org.apache.catalina.connector.CoyoteInputStream#77119553; line: 1, column: 131] (through reference chain: x.y.z.Request["optional"]->x.y.zOptionalParametersMapType["customer"]
)
at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:673)
at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:659)
at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1365)
at org.codehaus.jackson.map.deser.BeanDeserializer._handleUnknown(BeanDeserializer.java:725)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:703)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.xc.XmlAdapterJsonDeserializer.deserialize(XmlAdapterJsonDeserializer.java:59)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:299)
at org.codehaus.jackson.map.deser.SettableBeanProperty$FieldProperty.deserializeAndSet(SettableBeanProperty.java:579)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:697)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2704)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1315)
at org.codehaus.jackson.jaxrs.JacksonJsonProvider.readFrom(JacksonJsonProvider.java:419)
So, how can I prevent RestEasy from using the JAXB annotations for marshalling to and from JSON?
Here is the request class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "domainRecommendationRequest")
public class Request {
#XmlJavaTypeAdapter(OptionalParametersXmlAdapter.class)
private Map<String, List<String>> optional = new HashMap<>();
}
Here is the XmlAdapter:
#Override
public class OptionalParametersXmlAdapter extends XmlAdapter<OptionalParametersMapType, Map<String, List<String>>> {
public OptionalParametersMapType marshal(Map<String, List<String>> v) throws Exception {
OptionalParametersMapType result = new OptionalParametersMapType();
List<OptionalParameterItemType> optionalParameterItemTypes = new ArrayList<>();
Set<String> keySet = v.keySet();
for (String parameterName : keySet) {
OptionalParameterItemType item = new OptionalParameterItemType();
item.name = parameterName;
item.values = v.get(parameterName);
optionalParameterItemTypes.add(item);
}
result.parameter = optionalParameterItemTypes;
return result;
}
}
Here is the wrapper for the map:
public class OptionalParametersMapType {
public List<OptionalParameterItemType> parameter = new ArrayList<>();
}
Here is the actual map entry item:
public class OptionalParameterItemType {
#XmlAttribute
public String name;
#XmlElementWrapper(name = "values")
#XmlElement(name = "value")
public List<String> values = new ArrayList<>();
}
This is what I expect in the JSON request:
{"optional":{"customer":["Mike"]}}
As you can see, I do intend to have a different format in XML.
The problem is resteasy-jackson-provider depends on jackson-module-jaxb-annotations, which is used to map JAXB annotations/annotated classes to JSON. Now in a normal explicit use of ObjectMapper, in order to make use of this module, we would need to explicitly register this module like (See here)
ObjectMapper objectMapper = new ObjectMapper();
JaxbAnnotationModule module = new JaxbAnnotationModule();
objectMapper.registerModule(module);
-- OR --
AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
objectMapper.setAnnotationIntrospector(introspector);
That being said, it appears (not confirmed with any facts, but looks probable) that when the ObjectMapper is being created for your serialization, when the JAXB annotations are noticed, the module is automatically registered.
I don't know of any possible annotations we can use to stop this, but one way to solve this problem is to create a ContextResolver for the ObjectMapper, where we don't register the JAXB module.
#Provider
public class ObjectMapperContextResolver
implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
return new ObjectMapper();
}
}
Once we register that with our JAX-RS application, it will be the context resolver used to get the ObjectMapper. We could configure the ObjectMapper further, but this is just an example. Test it and it works as expected.
I'm facing the following situation parsing a JSON.
The JSON I want to unmarshal contains an array of numbers (doubles) like this:
"position":[52.50325,13.39062]
So there is no name/value pairs.
The Problem is that I can't get the value of this array. In the Java object modeling the JSON I defined the position attribute as list of Doubles: List<Double> but after the unmarshel, the position attribute is always null.
For testing purpose I changed the content of the JSON like that:
position: [„52.50325“ ,“ 13.39062“ ]
and then there is no issue, I get the list with two elements. (Btw, this happens regardless if the position is defined as list of Strings or list of Doubles (List<String> or List<Double>))
So a workaround could be to alter the JSON response and mark this numbers as string before unmarshaling it, but I would like to avoid that, and I’m wondering if there is solution to get the value of a number array?
Here is a snapshot from the code:
ResultsListDO.java:
#XmlElement(required = true)
protected List<Double> position;
public List<Double> getPosition()
{
if (position == null) {
position = new ArrayList<Double>();
}
return this.position;
}
JSON unmarshal:
context = new JSONJAXBContext(JSONConfiguration.mapped().build(), ResultsListDO.class);
JSONUnmarshaller um = context.createJSONUnmarshaller();
ResultsListDO resultsList = um.unmarshalFromJSON(source.getReader(), ResultsListDO.class);
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
Unless you have annotated your class with #XmlAccessorType(XmlAccessType.FIELD) the problem is probably that your annotation is on the field rather than the get method (see: http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html).
import java.util.*;
import javax.xml.bind.annotation.XmlElement;
public class Foo {
protected List<Double> position;
#XmlElement(required = true)
public List<Double> getPosition()
{
if (position == null) {
position = new ArrayList<Double>();
}
return this.position;
}
}
Demo Code
Below I'll demonstrate that everything works using MOXy as the JSON-binding provider.
jaxb.properties
To specify MOXy as your JAXB provider you need to include a file called jaxb.properties in the same package as your domain model with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Demo
import java.util.*;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String,Object>();
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {Foo.class}, properties);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StreamSource json = new StreamSource("src/forum18355753/input.json");
Foo foo = unmarshaller.unmarshal(json, Foo.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(foo, System.out);
}
}
input.json/Output
{
"position" : [ 52.50325, 13.39062 ]
}
Problem:
We are facing strange problems when marshalling JSONs objects including the following content {"#type":"xs:string"}. Marshalling of this object results in a NullPointerException. See the stack trace below:
java.lang.NullPointerException
at com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.startElement(SAX2DOM.java:204)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.closeStartTag(ToXMLSAXHandler.java:208)
at com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.characters(ToXMLSAXHandler.java:528)
at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerHandlerImpl.characters(TransformerHandlerImpl.java:172)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.DomLoader.text(DomLoader.java:128)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.text(UnmarshallingContext.java:499)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.text(InterningXmlVisitor.java:78)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.processText(StAXStreamConnector.java:324)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.handleEndElement(StAXStreamConnector.java:202)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:171)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:355)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:334)
at com.sun.jersey.json.impl.BaseJSONUnmarshaller.unmarshalJAXBElementFromJSON(BaseJSONUnmarshaller.java:108)
at com.sun.jersey.json.impl.BaseJSONUnmarshaller.unmarshalFromJSON(BaseJSONUnmarshaller.java:97)
at JerseyNPETest.testNPEUnmarshal(JerseyNPETest.java:20)
The problem occurs while getting the response from the external service and casting it implicity by glassfish (Simple REST call).
We investigated the problem and found that it is actually related to the JSON unmarshaller.
Testcase:
Marshalling -
To verify our finding, we created a class which contains a member of type Object named propertyA. Then we set the value of propertyA to "some value" and marshalled it using the default marshaller which results in the JSON string "{"#type":"xs:string","$":"some value"}".
Unmarshalling - Afterwards we used the default unmarsahller. The attempt to unmarshall this JSON string resulted in the mentioned exception.
See the test case below:
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.json.impl.BaseJSONUnmarshaller;
import org.junit.Test;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.StringReader;
public class JerseyNPETest {
private static final String ERROR = "{\"additionalObject\":{\"#type\":\"xs:string\",\"$\":\"some value\"}}";
#Test
public void testNPEUnmarshal() throws JAXBException {
final JAXBContext context = JAXBContext.newInstance(AnObject.class);
final JSONConfiguration jsonConfig = JSONConfiguration.DEFAULT;
final BaseJSONUnmarshaller unmarshaller = new BaseJSONUnmarshaller(context, jsonConfig);
final StringReader reader = new StringReader(ERROR);
final AnObject result = unmarshaller.unmarshalFromJSON(reader, AnObject.class);
}
#XmlRootElement
public static class AnObject {
private Object additionalObject;
public Object getAdditionalObject() {
return additionalObject;
}
public void setAdditionalObject(final Object additionalObject) {
this.additionalObject = additionalObject;
}
}
}
Question:
How could this be solved in general e.g. by some configuration of glassfish to avoid this issue in the first place?
Currently we are working with glassfish 3.1.2.2. Any help is much appreciated!
My Java code returns a Collection (ArrayList) and the resulting JSON produced by JAXB looks like:
{"todo":[{"name":"CAMPBELL","sales":"3","time":"1331662363931"},
{"name":"FRESNO","sales":"2","time":"1331662363931"}]}
But, is there a way I can make it look like:
[{"name":"CAMPBELL","sales":"3","time":"1331662363931"},
{"name":"FRESNO","sales":"2","time":"1331662363931"}]
Is there a way in Java/JAXB or maybe in AJAX callback using responseText. .
BTW, I've also tried with Java array but it made no difference.
Any help will be appreciated.
All you need is:
var todo = resp.todo;
where resp is the whole JSON response. Note that this is actually good design. A root array is not recommended due to JSON hijacking.
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
Instead of correcting bad JSON on the client, you could fix the problem on the server. Below is an example of how EclipseLink JAXB (MOXy) could be used to produce the desired JSON:
Demo
package forum9689970;
import java.io.StringReader;
import java.util.List;
import javax.xml.bind.*;
import javax.xml.transform.stream.StreamSource;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(SalesPerson.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setProperty("eclipselink.media-type", "application/json");
unmarshaller.setProperty("eclipselink.json.include-root", false);
String jsonString = "[{\"name\":\"CAMPBELL\",\"sales\":\"3\",\"time\":\"1331662363931\"},{\"name\":\"FRESNO\",\"sales\":\"2\",\"time\":\"1331662363931\"}]";
StreamSource json = new StreamSource(new StringReader(jsonString));
List<SalesPerson> salesPeople = (List<SalesPerson>) unmarshaller.unmarshal(json, SalesPerson.class).getValue();
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.setProperty("eclipselink.json.include-root", false);
marshaller.marshal(salesPeople, System.out);
}
}
SalesPerson
package forum9689970;
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class SalesPerson {
private String name;
private String sales;
private String time;
}
Output
[ {
"name" : "CAMPBELL",
"sales" : "3",
"time" : "1331662363931"
}, {
"name" : "FRESNO",
"sales" : "2",
"time" : "1331662363931"
} ]
For More Information
MOXy as Your JAX-RS JSON Provider - Server Side
JSON Binding with EclipseLink MOXy - Twitter Example
Specifying EclipseLink MOXy as Your JAXB Provider
I'm about to develop a JAX-RS based RESTful web service and I use MOXy (JAXB) in order to automatically generate my web service's JSON responses.
Everything is cool, but due to the fact that the web service will be the back-end of a JavaScript-based web application and therefore publicly accessible I don't want to expose certain details like class names, etc.
But, I've realized that under certain conditions MOXy embeds a "#type" entry into the marshalled string and this entry is followed by the class name of the object that has just been marshalled.
In particular, I've realized that MOXy behaves in this way when marshalling instances of extended classes.
Assume the following super class "MyBasicResponse"
#XmlRootElement(name="res")
public class MyBasicResponse {
#XmlElement
private String msg;
public MyBasicResponse() {
// Just for conformity
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
And this specialized (extended) class "MySpecialResponse"
#XmlRootElement(name="res")
public class MySpecialResponse extends MyBasicResponse {
#XmlElement
private String moreInfo;
public MySpecialResponse() {
// Just for conformity
}
public String getMoreInfo() {
return moreInfo;
}
public void setMoreInfo(String moreInfo) {
this.moreInfo = moreInfo;
}
}
So, the MyBasicResponse object's marshalled string is
{"msg":"A Message."}
(That's okay!)
But, the MySpecialResponse object's marshalled string is like
{"#type":"MySpecialResponse","msg":"A Message.","moreInfo":"More Information."}
Is there a way to strip the
"#type":"MySpecialResponse"
out of my response?
You can wrap your object in an instance of JAXBElement specifying the subclass being marshalled to get rid of the type key. Below is a full example.
Java Model
Same as from the question, but with the following package-info class added to specifying the field access to match those classes
#XmlAccessorType(XmlAccessType.FIELD)
package com.example.foo;
import javax.xml.bind.annotation.*;
Demo Code
Demo
import java.util.*;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {MySpecialResponse.class}, properties);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
MySpecialResponse msr = new MySpecialResponse();
marshaller.marshal(msr, System.out);
JAXBElement<MySpecialResponse> jaxbElement = new JAXBElement(new QName(""), MySpecialResponse.class, msr);
marshaller.marshal(jaxbElement, System.out);
}
}
Output
We see that when the object was marshalled an type key was marshalled (corresponding to the xsi:type attribute in the XML representation), because as MOXy is concerned it was necessary to distinguish between MyBasicResponse and MySpecialResponse. When we wrapped the object in an instance of JAXBElement and qualified the type MOXy didn't need to add the type key.
{
"type" : "mySpecialResponse"
}
{
}
For More Information
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://blog.bdoughan.com/2012/05/moxy-as-your-jax-rs-json-provider.html