Search code examples
javajsonfasterxml

Injecting json property based on condition using Jackson


I have a json format which I am converting into Java Object Model using Jackson API. I am using Jaxsonxml 2.1.5 parser. The json response is as shown below.

 {
   "response": {
   "name": "states",
   "total-records": "1",
   "content": {
     "data": {
       "name": "OK",
       "details": {
         "id": "1234",
         "name": "Oklahoma"
       }
     }
   }
 }
}

Now json response format has changed. If the total-records is 1 the details will be an object with id and name attributes. But if the total-records is more than 1 then the details will be an array of object like below:

    {
      "response": {
        "name": "states",
        "total-records": "4",
        "content": {
          "data": {
            "name": "OK",
            "details": [
              {
                "id": "1234",
                "name": "Oklahoma"
              },
              {
                "id": "1235",
                "name": "Utah"
              },
              {
                "id": "1236",
                "name": "Texas"
              },
              {
                "id": "1237",
                "name": "Arizona"
              }
            ]
          }
        }
      }
    }

My Java Mapper class looks like below with earlier json response.

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class MapModelResponseList {

      @JsonProperty("name")
      private String name;

      @JsonProperty("total-records")
      private String records;

      @JsonProperty(content")
      private Model model;

      public Model getModelResponse() {
        return model;
      }

      public void setModel(Model model) {
        this.model = model;
      }
    }

Client Code

    package com.test.deserializer;

    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com..schema.model.Person;

    public class TestClient {

        public static void main(String[] args) {
            String response1="{\"id\":1234,\"name\":\"Pradeep\"}";
            TestClient client = new TestClient();
            try {
                Person response = client.readJSONResponse(response1, Person.class);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public <T extends Object> T readJSONResponse(String response, Class<T> type) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
            T result = null;
            try {
                result = mapper.readValue(response, type);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return (T) result;
        }

    }

Now based on the total-records how to handle to mapping to either a Model or list of Model Object. Please let me know.


Solution

  • You need a custom deserializer. The idea is to mix and match object processing with tree processing. Parse objects where possible but use the tree (JSONNode) for custom handling.

    On the MapModelResponseList, remove the records property and add a List<Data> array where Data is just a holder class for the id/name pairs. You can get the total records by returning the size of this list.

    In the deserializer, do the following:

    public final class MapModelDeserializer extends BeanDeserializer {
       public MapModelDeserializer(BeanDeserializerBase src) {
        super(src);
       }
    
      protected void handleUnknownProperty(JsonParser jp, DeserializationContext ctxt, Object beanOrClass, String propName) throws IOException, JsonProcessingException {
        if ("content".equals(propName)) {
          MapModelResponseList response = (MapModelResponseList) beanOrClass;
    
          // this probably needs null checks!
          JsonNode details = (JsonNode) jp.getCodec().readTree(jp).get("data").get("details");
    
          // read as array and create a Data object for each element
          if (details.isArray()) {
            List<Data> data = new java.util.ArrayList<Data>(details.size());
    
            for (int i = 0; i < details.size(); i++) {
               Data d = jp.getCodec().treeToValue(details.get(i), Data.class);
               data.add(d);
            }
    
            response.setData(data);
          }
          // read a single object
          else {
             Data d = jp.getCodec().treeToValue(details, Data.class);
             response.setData(java.util.Collections.singletonList(d));
          }
    
        super.handleUnknownProperty(jp, ctxt, beanOrClass, propName);
    }   
    

    Note that you do not implement deserialize() - the default implementation is used to create the MapModelResponseList as normal. handleUknownProperty() is used to deal with the content element. Other data you don't care about is ignored due to @JsonIgnoreProperties(ignoreUnknown = true) in the super call.