MOXyJsonProvider bug when unmarshalling json array - json

I have a simple jersey resource, UserContentManager, that processes a simple ContentInput class. Both classes are below. The postHello method works fine when called using curl -X POST -H "Content-Type: application/json" -d '{"id":1,"type":"a"}' localhost:50000/news/rest/hello but the putHello method is failing when called with curl -X PUT -H "Content-Type: application/json" -d '[{"id":1}]' localhost:50000/news/rest/hello
It is failing in MOXyJsonProvider:598 (the line in bold below) because when it is unmarshalled, it is unmarshalled to ArrayList<Property> instead of ArrayList<JAXBElement<Property>> as the code is expecting i.e. Object value = jaxbElement.getValue() is an ArrayList<Property> not ArrayList<JAXBElement> like the cast.
Is this a bug in Moxy or am I doing something wrong? The getArray method is working fine when returning an array. I have tried it with and without #XmlRootElement on the ContentInput class but the results are the same.
JAXBElement<?> jaxbElement = unmarshaller.unmarshal(jsonSource, domainClass);
if(type.isAssignableFrom(JAXBElement.class)) {
return jaxbElement;
} else {
Object value = jaxbElement.getValue();
if(value instanceof ArrayList) {
if(type.isArray()) {
ArrayList<JAXBElement> arrayList = (ArrayList<JAXBElement>) value;
int arrayListSize = arrayList.size();
Object array;
if(genericType instanceof GenericArrayType) {
array = Array.newInstance(JAXBElement.class, arrayListSize);
for(int x=0; x<arrayListSize; x++) {
Array.set(array, x, arrayList.get(x));
}
} else {
array = Array.newInstance(domainClass, arrayListSize);
for(int x=0; x<arrayListSize; x++) {
* Array.set(array, x, arrayList.get(x).getValue());*
}
}
return array;
#WebService
#Path("/hello")
public class UserContentManager {
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response postHello(ContentInput input) {
input.setId(input.getId());
input.setType("clip" + input.getType());
ResponseBuilder builder = Response.ok();
builder.entity(input);
return builder.build();
}
#PUT
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public ContentInput[] putHello(ContentInput [] input) {
return input;
}
#Path("/array")
#GET
#Produces(MediaType.APPLICATION_JSON)
public ContentInput[] getArray() {
return new ContentInput[] {
new ContentInput(),
new ContentInput()
};
}
}
public class ContentInput {
private int id;
private String type;
public ContentInput() {}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
You are hitting the following bug which we have fixed in EclipseLink 2.5.1:
http://bugs.eclipse.org/413760

Related

Post nested Json to spring controller using Jackson data binding

Well I am trying to retrieve a nested json in spring controller and getting "The request sent by the client was syntactically incorrect."
My code is working and getting the correct data binding if I don't do nested json format, so I can conclude that maybe something is not right in my DTO.
CURL Command:
CURL -i -H "Content-Type: application/json" -X POST http://localhost:8080/insertMapping -d '{"mapping": {"adobe_segment_id": "125", "dp_key_id": "1"}, "type": "adobe"}'
JSON:
{"mapping": {"adobe_segment_id": "125", "dp_key_id": "1"}, "type": "adobe"}
Controller:
#RequestMapping(value = "/insertMapping", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> createUser(#RequestBody RequestBodyDTO mapping) {
LOG.info("/insertMapping" + " ,type:" + mapping.getType().getType());
return null;
}
RequestBodyDTO:
public class RequestBodyDTO {
private MappingDTO mapping;
private TypeDTO type;
public TypeDTO getType() {
return type;
}
public void setType(TypeDTO type) {
this.type = type;
}
public MappingDTO getMapping() {
return mapping;
}
public void setMapping(MappingDTO mapping) {
this.mapping = mapping;
}
}
MappingDTO:
public class MappingDTO {
// adobe
private Integer adobe_segment_id;
private Integer dp_key_id;
public Integer getAdobe_segment_id() {
return adobe_segment_id;
}
public void setAdobe_segment_id(Integer adobe_segment_id) {
this.adobe_segment_id = adobe_segment_id;
}
public Integer getDp_key_id() {
return dp_key_id;
}
public void setDp_key_id(Integer dp_key_id) {
this.dp_key_id = dp_key_id;
}
}
TypeDTO:
public class TypeDTO {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
Problem resolved after I changed "private TypeDTO type" to "private String type".

Simple way to strip outer array of responses in gson

I'm working with an api (Phillips Hue) that wraps all of it's json responses in an array with one entry (the content).
Example:
[{
"error": {
"type": 5,
"address": "/",
"description": "invalid/missing parameters in body"
}
}]
I usually write standard POJO's parsed by GSON to handle responses but since the response is not a json object I'm a bit stumped on the best way to deal with this. I didn't really want every object to actually be an array that I have to call .get(0) on.
Example of the POJO if it was a JSON obj and NOT wrapped in an array.
public class DeviceUserResponse {
private DeviceUser success;
private Error error;
public DeviceUser getSuccess() {
return success;
}
public Error getError() {
return error;
}
public static class Error {
private int type;
private String address;
private String description;
public int getType() {
return type;
}
public String getAddress() {
return address;
}
public String getDescription() {
return description;
}
#Override
public String toString() {
return "Type: " + this.type
+ " Address: " + this.address
+ " Description: " + this.description;
}
}
}
What I have to do right now:
ArrayList<DeviceUserResponse> response.get(0).getError();
Is there a way that I can strip this array for every response or am I just going to have to do a .get(0) in my POJO's and just not expose it?
I think you've to go with custom deserialization in order to "strip out" the array.
Here a possible solution.
An adapter for your response POJO:
public class DeviceUserResponseAdapter extends TypeAdapter<DeviceUserResponse> {
protected TypeAdapter<DeviceUserResponse> defaultAdapter;
public DeviceUserResponseAdapter(TypeAdapter<DeviceUserResponse> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
#Override
public void write(JsonWriter out, DeviceUserResponse value) throws IOException {
defaultAdapter.write(out, value);
}
#Override
public DeviceUserResponse read(JsonReader in) throws IOException {
in.beginArray();
assert(in.hasNext());
DeviceUserResponse response = defaultAdapter.read(in);
in.endArray();
return response;
}
}
A factory for your adapter:
public class DeviceUserResponseAdapterFactory implements TypeAdapterFactory {
#Override
#SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if (type.getRawType()!=DeviceUserResponse.class) return null;
TypeAdapter<DeviceUserResponse> defaultAdapter = (TypeAdapter<DeviceUserResponse>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new DeviceUserResponseAdapter(defaultAdapter);
}
}
Then you've to register and user it:
DeviceUserResponseAdapterFactory adapterFactory = new DeviceUserResponseAdapterFactory();
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.registerTypeAdapterFactory(adapterFactory).create();
DeviceUserResponse response = gson.fromJson(json, DeviceUserResponse.class);
System.out.println(response.getError());
This solution will not work if you have the DeviceUserResponse inside other complex JSON object. I that case the adapter will try to find an array and will terminate with an error.
Another solution is to parse it as array and then in your "communication" layer you get only the first element. This will preserve the GSon deserialization.
In the comment you're asking for a more generic solution, here one:
The adapter:
public class ResponseAdapter<T> extends TypeAdapter<T> {
protected TypeAdapter<T> defaultAdapter;
public ResponseAdapter(TypeAdapter<T> defaultAdapter) {
this.defaultAdapter = defaultAdapter;
}
#Override
public void write(JsonWriter out, T value) throws IOException {
defaultAdapter.write(out, value);
}
#Override
public T read(JsonReader in) throws IOException {
in.beginArray();
assert(in.hasNext());
T response = defaultAdapter.read(in);
in.endArray();
return response;
}
}
The factory:
public class ResponseAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
if ((type.getRawType().getSuperclass() != Response.class)) return null;
TypeAdapter<T> defaultAdapter = (TypeAdapter<T>) gson.getDelegateAdapter(this, type);
return (TypeAdapter<T>) new ResponseAdapter<T>(defaultAdapter);
}
}
Where Response.class is your super class from which all the service responses inherit.
The first solution advices are still valid.

How to parse input stream using net.sf.json library

Following is the response in Json i am getting after making get request to Http API Format,
[{"name":"test","tracing":false},{"name":"dyn1","tracing":false},
{"name":"dyn2","tracing":false},{"name":"esb","tracing":false}]
Could you please post a sample code to parse this json object to get individual Host object with name and tracing.
Thanks,
Amol
String myDataAsAString = "[{\"name\":\"test\",\"tracing\":false},{\"name\":\"dyn1\",\"tracing\":false},
{\"name\":\"dyn2\",\"tracing\":false},{\"name\":\"esb\",\"tracing\":false}]";
JSONArray hostArray = JSONArray.fromObject(myDataAsAString);
for(int i = 0; i < hostArray.size(); i++)
{
JSONObject hostObject = hostArray.getJSONObject(i);
String hostName = hostObject.getString("name");
boolean tracing = hostObject.getBoolean("tracing");
...your code for each element here
}
You can yse GSON library http://code.google.com/p/google-gson/
Try:
import com.google.gson.Gson;
public class Test {
public static void main(String[] args){
String json = "[{'name':'test','tracing':false},{'name':'dyn1','tracing':false},\n" +
"{'name':'dyn2','tracing':false},{'name':'esb','tracing':false}]";
HostObj[] hostObjects = new Gson().fromJson(json, HostObj[].class);
}
class HostObj {
private String name;
private boolean tracing;
HostObj() {
}
public String getName() {
return name;
}
public void setName(String name) {
name = name;
}
public boolean isTracing() {
return tracing;
}
public void setTracing(boolean tracing) {
tracing = tracing;
}
}
}

Jackson in Spring: how to unmarshal a "generic" class w/o it thinking it's a LinkedHashMap?

So my entities look like this:
public class HappyClass<T>
{
private String id;
prviate int ver;
private Object obj;
public String getId()
{
return this.id;
}
public void setId( String id )
{
this.id = id;
}
public int getVer()
{
return this.ver;
}
public void setVer( int ver )
{
this.ver = ver;
}
#JsonTypeInfo( use = Id.NONE )
public T getObj()
{
return obj;
}
public void setObj( T obj )
{
this.obj = obj;
}
}
public class HappyGeneric
{
private String someStuff();
public String getSomeStuff()
{
return this.someStuff();
}
public void setSomeStuff( String someStuff )
{
this.someStuff = someStuff;
}
}
If I instantiate a class like this:
HappyClass<HappyGeneric> hc = new HappyClass<HappyGeneric>();
If I send it to Spring in a #ResponseBody it returns this:
{
"id" : "iamsomeid",
"ver" : 123,
"obj" : {
"someStuff" : "iamsomestuff"
}
}
However, when Spring and/or Jackson attempts to unmarshal the same JSON, it figures out that the main class is a HappyClass, however, the getObj() it unmarshals to a LinkedHashMap and not a HappyGeneric no matter what I seem to annotate it with.
Anybody have any ideas how I can force Jackson to unmarshal that generic to the original class?
Thanks!
EDIT: I'm aware I can call mapper.convertValue( blah.getObj(), HappyGeneric.class ) and get the object out that way-- I was hoping to get Spring to figure it out automatically (through annotations, for example).

Unmarshalling JSON array via Jettison/Resteasy

Ran into a similar problem like the following forum post:
http://jersey.576304.n2.nabble.com/parsing-JSON-with-Arrays-using-Jettison-td5732207.html
Using Resteasy 2.0.1GA with Jettison 1.2 and getting a problem marshalling arrays when involving namespace mappings. See code below. Basically if the number of array entries are greater than one and namespace mappings are used. Anybody else run into this problem? The Nabble form poster got around it by writing a custom unmarshaller.
I either need to isolate the Jettison bug or write a Resteasy extension of the JettisonMappedUnmarshaller class (which hands over the namespace mappings and unmarshaller to the Jettison Configuration).
The following code doesn't unmarshall (post step) if the properties variables contains 2 or more entries.
public class Experimenting {
#Path("test")
public static class MyResource {
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "Property", propOrder = { "name", "value" })
public static class MyProperty {
#XmlElement(name = "Name", required = true)
protected String name;
#XmlElement(name = "Value", required = true)
protected String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#XmlType(name = "MyElement", propOrder = { "myProperty" })
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name = "MyElement", namespace = "http://www.klistret.com/cmdb/ci/commons")
#Mapped(namespaceMap = { #XmlNsMap(namespace = "http://www.klistret.com/cmdb/ci/commons", jsonName = "com.klistret.cmdb.ci.commons") })
public static class MyElement {
#XmlElement(name = "MyProperty", namespace = "http://www.klistret.com/cmdb/ci/commons")
protected List myProperty;
public List getMyProperty() {
if (myProperty == null) {
myProperty = new ArrayList();
}
return this.myProperty;
}
public void setMyProperty(List myProperty) {
this.myProperty = myProperty;
}
}
#GET
#Path("myElement/{id}")
#Produces(MediaType.APPLICATION_JSON)
public MyElement getMy(#PathParam("id")
Long id) {
MyElement myElement = new MyElement();
MyProperty example = new MyProperty();
example.setName("example");
example.setValue("of a property");
MyProperty another = new MyProperty();
another.setName("another");
another.setValue("just a test");
MyProperty[] properties = new MyProperty[] { example, another };
myElement.setMyProperty(Arrays.asList(properties));
return myElement;
}
#POST
#Path("/myElement")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public MyElement createMy(MyElement myElement) {
List properties = myElement.getMyProperty();
System.out.println("Properties size: " + properties.size());
return myElement;
}
}
private Dispatcher dispatcher;
#Before
public void setUp() throws Exception {
// embedded server
dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addPerRequestResource(MyResource.class);
}
#Test
public void getAndCreate() throws URISyntaxException,
UnsupportedEncodingException {
MockHttpRequest getRequest = MockHttpRequest.get("/test/element/44");
MockHttpResponse getResponse = new MockHttpResponse();
dispatcher.invoke(getRequest, getResponse);
String getResponseBodyAsString = getResponse.getContentAsString();
System.out.println(String.format(
"Get Response code [%s] with payload [%s]", getResponse
.getStatus(), getResponse.getContentAsString()));
MockHttpRequest postRequest = MockHttpRequest.post("/test/element");
MockHttpResponse postResponse = new MockHttpResponse();
postRequest.contentType(MediaType.APPLICATION_JSON);
postRequest.content(getResponseBodyAsString.getBytes("UTF-8"));
dispatcher.invoke(postRequest, postResponse);
System.out.println(String.format(
"Post Response code [%s] with payload [%s]", postResponse
.getStatus(), postResponse.getContentAsString()));
}
}
Do you have to use Jettison? If not I would recommend just switching to use Jackson instead; this typically solves array/list related problems (problem with Jettison is that it converts to XML model, which makes it very hard to tell arrays from objects -- there are bugs, too, but it is fundamentally hard thing to get working correctly).