Search code examples
javajsonpojoobjectmapper

Mapping a trimmed response using ObjectMapper Or wrapping the response obtained


Details ---

One of my POJO SomeResponseObject for an api response has attribute

@JsonProperty("s_summary")
private Map<String, SummaryObject> summary 

which further has few more attributes. These are summed in json as follows :

{  
 "s_summary": {
    "rewardSubscription": {
      "accountId": "XYZ",
      "startDate": "2015-12-29T19:00:00+05:30",
      "endDate": "2017-06-21T00:00:00+05:30",
      "isActive": true,
      "entityId": "ID123",
      "status": "ACTIVE"
    }
  }
}

This POJO(json) is further modified by our service to return a RESPONSE as :

{  
  "rewardSubscription": {
    "accountId": "XYZ",
    "startDate": "2015-12-29T19:00:00+05:30",
    "endDate": "2017-06-21T00:00:00+05:30",
    "isActive": true,
    "entityId": "ID123",
    "status": "ACTIVE"
  }
}

Narrowing Down ---

Now when we are writing tests against this API call. We end up being unable to map the response to any specific POJOs(java response class). Test code -

JSONObject responseObject = new JSONObject(responseFromService.getResponseBody())
      .getJSONObject("RESPONSE");
ObjectMapper objectMapper = new ObjectMapper();
SomeResponseObject summaryResponse = objectMapper.getObjectMapper()
      .readValue(responseObject.toString(), SomeResponseObject.class); // And this wouldn't work.

Question --

Is there any way we can cast the current API response or wrap it somehow to be mapped to the actual POJO(SomeResponseObject.class)?

Thanks in advance.


Solution

  • Problem

    You receive an object with a rewardSubscription field, or, in your case, a map, with a rewardSubscription key. You can't convert a map to an object of SomeResponseObject type directly.

    Solution

    Option 1

    Convert json to a map manually and set it to the SomeResponseObject instance:

    JSONObject responseObject = new JSONObject(responseFromService.getResponseBody())
          .getJSONObject("RESPONSE");
    
    ObjectMapper objectMapper = new ObjectMapper();
    Map<String, SummaryObject> summaryMap = objectMapper.readValue(responseObject.toString(), new TypeReference<Map<String, SummaryObject>>() {});
    SomeResponseObject response = new SomeResponseObject();
    response.setSummaryMap(summaryMap);
    

    Option 2

    So as not to manually convert map each time, write a custom deserializer that will handle both cases. The deserialize method should be similar to this:

    @Override
    public SomeResponseObject deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = jp.readValueAsTree();
    
        JsonNode sSummaryNode = rootNode.get("s_summary");
        if (sSummaryNode != null) {
            // Usual case.
            return objectMapper.treeToValue(sSummaryNode, SomeResponseObject.class);
        } else {
            // Special case - when received a map.
            Map<String, SummaryObject> summaryMap = objectMapper.readValue(rootNode.toString(), new TypeReference<Map<String, SummaryObject>>() {});
            SomeResponseObject response = new SomeResponseObject();
            response.setSummaryMap(summaryMap);
            return response;
        }
    }
    

    And then in the code you don't care:

    ObjectMapper objectMapper = new ObjectMapper();
    SomeResponseObject response = objectMapper.readValue(json, SomeResponseObject.class);