Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing JSON with multiple types in one field

I would like to deserialize JSON (with Jackson 1.9.11 and RestTemplate 1.0.1), in which one field may have more type meanings, for example:

    {"responseId":123,"response":"error"}

or

    {"responseId":123,"response":{"foo":"bar", ... }}

Either one or other case works correctly with one setter of specific type (String od custom Response class), but when I put into my entity bean overriden setter to be able to handle both cases, exception is thrown:

Caused by: org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [xxxx.templates.ExportResponse] and content type [application/json;charset=utf-8]

I was thinking about three solutions, but I did not get any of them working:

  • using only String setter and inside use ObjectMapper to unmarshall that string, if it is not equal to "error", but when that JS Array comes, it's not string so no String setter is used :(.
  • use polymorphic type handling (@JsonTypeInfo annotation) with own JsonDeserializer extension - I'm still trying to understand this and implement.
  • create list of HttpMessageConverter and put inside all message converters, I can use. But I thing this step is unnecessary, because only MappingJacksonHttpMessageConverter is used, am I right?

EDIT: how it works now

Setter in entity bean:

@JsonDeserialize(using = ResponseDeserializer.class)
public void setResponse(Object responseObject) {
    if(responseObject instanceof Response)
        response = (Response) responseObject;
}

Deserialize method in ResponseDeserializer:

public Response deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
    Response response = new Response();

    if(JsonToken.START_OBJECT.equals(parser.getCurrentToken())) {
        ObjectMapper mapper = new ObjectMapper();
        response = mapper.readValue(parser, Response.class);
    } else
        throw new JsonMappingException("Unexpected token received.");

    return response;
}
like image 891
shmoula Avatar asked Dec 15 '12 10:12

shmoula


1 Answers

The only way to achieve that is to use a custom deserializer.

Here is an example:

ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addDeserializer(Response.class, new ResponseJsonDeserializer());
mapper.registerModule(testModule);

And here is how to write (how I would write it at least) the deserializer:

class ResponseJsonDeserializer extends JsonDeserializer<Response>  {
  @Override
  public Responsedeserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
    Response response = new Response();
    if(jp.getCurrentToken() == JsonToken.VALUE_STRING) {
        response.setError(jp.getText());
    } else {
       // Deserialize object
    }
    return response;
  }
}

class Response {
   private String error;
   private Object otherObject; // Use the real type of your object

   public boolean isError() {
      return error != null;
   }

   // Getters and setters

}
like image 87
aymeric Avatar answered Sep 16 '22 11:09

aymeric