I’m trying to run batch requests against Facebook with Jersey. The problem is Facebook returns the strangest of structures – a mix of JSONObject and JSONString:
[
{
"code": 200,
"headers": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
},
<!-- some more headers... -->
],
"body": "{\n \"message\": \"Hello World!\",\n \"id\": \"...\",\n \"created_time\": \"2012-10-17T07:18:02+0000\"\n
<!-- ... -->
}"
}
]
Now when I try to use the Jackson ObjectMapper to deserialize this mess, I get a
JsonMappingException: Can not instantiate value of type [simple type, class package.to.Post] from JSON String; no single-String constructor/factory method (through reference chain: package.to.BatchResult["body"])
This is the POJO structure I’m using:
public class BatchResult<T> {
private T body;
private int code;
private List<BatchResultHeader> headers;
// ...
}
public class BatchResultHeader {
private String name;
private String value;
// ...
}
public class Post {
private String message;
// ...
}
I send the batch request like this. Params contains the batch parameter and the batch request as defined in the documentation. Also required is the POST call for batch requests. So like I said the call should be fine, as the resulting JSON is as expected (see above):
Client client = Client.create();
WebResource webResource = client.resource("https://graph.facebook.com");
ClientResponse response = webResource.type("application/x-www-form-urlencoded")
.post(ClientResponse.class, params);
String json = response.getEntity(String.class);
Now I just use the ObjectMapper to deserialize:
TypeReference<List<BatchResult<Post>>> ref = new TypeReference<List<BatchResult<Post>>>(){});
ObjectMapper mapper = new ObjectMapper();
List<BatchResult<Post>> batchResults = mapper.readValue(json, ref);
Using @JsonCreator?
So when search for this exception I found the suggestion to use the @JsonCreator annotation with my Post constructor. That however leads to a
java.lang.IllegalArgumentException: Argument #0 of constructor [constructor for package.to.Post, annotations: {interface org.codehaus.jackson.annotate.JsonCreator=@org.codehaus.jackson.annotate.JsonCreator()}] has no property name annotation; must have name when multiple-paramater constructor annotated as Creator
The solution to this seems to be to annotate each of the POJO’s properties individually and that’s the point where I said: No thank you, certainly not!
So the question remains: Is there any way to deserialize this mix maybe with an ObjectMapper setting?
Or maybe I can tell Jersey to filter the incoming formatted String and cut all the whitespaces before sending it to Jackson for deserialization?
My unsatisfying Workaround:
When I tell my BatchResult class that body is a String, it works and I get a BatchResult with a String body. Now using the ObjectMapper again on that String body with the Post.class type it deserializes correctly. This is my current solution but it looks messy and it should not be my job to iterate over the deserialization results to deserialize its elements… Why can’t Jackson figure this out on its own?
Just Getting the Body as a Post
If you are not looking to deserialize the body, then you just need to give the Post object a constructor that takes type string:
Unmarshalling the Body into Post
If you would instead like to populate the Post object with the JSON contained in the string, you can use a JsonDeserializer. First, you will need to define a deserializer that reads in the string value and then unmarshalls it. Here is an example implementation:
Then you need to add a @JsonDeserialize annotation to the body field, specifying the deserializer to use:
You should now be able to get embedded JSON into your Post objects.