7

I need to convert the following JSON to Java object. The property providerResponse in the JSON contains map of properties but they are escaped and wrapped in doubleQuotes. As a result, it does not deserialize the property providerResponse into a Java object (it comes as String). I use objectMapper.readValue(msgStr, classType) to deserialize the JSON. The message is generated by AWS for SNS delivery status notifications and I don't have control to change the JSON message. Is it possible to configure ObjectMapper to unescape the property and deserialize into a Java object instead of String?

{  
   "delivery":{  
      "providerResponse":"{\"sqsRequestId\":\"308ee0c6-7d51-57b0-a472-af8e6c41be0b\",\"sqsMessageId\":\"88dd59eb-c34d-4e4d-bb27-7e0d226daa2a\"}"
   }
}

@JsonProperty("providerResponse")
private String providerResponse;
blacktide
  • 10,654
  • 8
  • 33
  • 53
user1573133
  • 904
  • 1
  • 9
  • 23
  • 1
    I've used gson for this, which forces you to create a class that defines the json, including any other classes. That is, we don't want to accept this label as a string, but rather tell the serializer what sort of object it is. –  Apr 06 '16 at 21:54
  • thx jdv. gson has not been used in this project so far. will review. – user1573133 Apr 07 '16 at 15:10
  • Oh, I assumed Jackson has the same functionality, which according to the answer you have accepted, seems to be the case -- a class and annotations for mapping JSON <-> objects. –  Apr 07 '16 at 20:25

2 Answers2

8

There doesn't seem to be a way to configure ObjectMapper to handle this behavior by default. The solution is to create a custom JsonDeserializer:

public class Wrapper {
    public Delivery delivery;
}

public class Delivery {
    @JsonDeserialize(using = ProviderResponseDeserializer.class)
    public ProviderResponse providerResponse;
}

public class ProviderResponse {
    public String sqsRequestId;
    public String sqsMessageId;
}

public class ProviderResponseDeserializer extends JsonDeserializer<ProviderResponse> {
    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public ProviderResponse deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        return mapper.readValue(jsonParser.getText(), ProviderResponse.class);
    }
}

Then you can deserialize the JSON by using your ObjectMapper:

ObjectMapper mapper = new ObjectMapper();
Wrapper wrapper = mapper.readValue(JSON, Wrapper.class);
blacktide
  • 10,654
  • 8
  • 33
  • 53
  • thx Casey. works like a charm. Would it be okay to instantiate ObjectMapper() every time we deserialize a message? Would it have performance hits for system under heavy load (1 million deserializations in a day)? – user1573133 Apr 07 '16 at 15:12
  • 1
    Glad to hear it! I don't think it would be an issue at all. If you'd rather not instantiate it every time though, [`ObjectMapper` is thread safe](http://stackoverflow.com/a/3909846/2446208), so it's completely fine to use a `static` instance of it inside your `ProviderResponseDeserializer` (updated the answer as this seems to be the preferred way). – blacktide Apr 07 '16 at 15:45
  • @Casey : I have this similar issue. I tried your solution. I get an error saying `no single string constructor defined`. I searched for the solution for this error. I came to know that I have to define a constructor for `ProviderResponse` class which takes the json string as an argument and then sets the properties from that json. Any idea ? – mantri Jul 08 '17 at 03:43
1

I faced this similar issue. This gets resolved if we define a constructor in ProviderResponse which takes a single string argument (which is actually json) and then map the json in the constructor to the instance of ProviderResponse and use this temp instance to initialise the properties.

public class Wrapper {
    public Delivery delivery;
}

public class Delivery {
    public ProviderResponse providerResponse;
}

public class ProviderResponse {
    public String sqsRequestId;
    public String sqsMessageId;

    private static ObjectMapper objMapper = new ObjectMapper();

    public ProviderResponse(String json) {
        ProviderResponse temp = objMapper.readValue(json, ProviderResponse.class);
        this.sqsMessageId = temp.sqsMessageId;
        this.sqsRequestId = temp.sqsRequestId;
    }
}

The key is to keep the ObjectMapper instance and the its usage somewhere in your utility class and use it from there.

mantri
  • 3,031
  • 4
  • 16
  • 19