2

I am using the Jackson for the Deseilization of the JSON. The Deseilization works perfectly for a JSON with CustomerDocument. However, I have a new requirement in which I need to find whether provided JSON has CustomerDocument or just Customer.

I am able to develop the logic for both but the problem is that when I try to merge it won't work for CustomerDocument. I am looking for a solution that would work for both. All I would like to do is build the logic to differentiate the incoming JSON based on customerDocument and single Customer.

Following is the CustomerDocument JSON:

{
  "isA": "CustomerDocument",
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}

Customer.class:

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer {
    private String isA;
    private String name;
    private String age;
}

JacksonMain:

public class JacksonMain {
    public static void main(String[] args) throws IOException {
        final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
        final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
        final ObjectMapper objectMapper = new ObjectMapper();
        jsonParser.setCodec(objectMapper);
        
        //Goto the start of the document
        jsonParser.nextToken();
        
        //Go until the customerList has been reached
        while (!jsonParser.getText().equals("customerList")) {
            jsonParser.nextToken();
        }
        jsonParser.nextToken();

        //Loop through each object within the customerList and deserilize them
        while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
            final JsonNode customerNode = jsonParser.readValueAsTree();
            final String eventType = customerNode.get("isA").asText();
            Object event = objectMapper.treeToValue(customerNode, Customer.class);
            System.out.println(event.toString());
        }
    }
}

The above code works perfectly and produces the following result:

Customer(isA=Customer, name=Batman, age=2008)

Scenario-2

Now user can provide the direct customer object without the customerDocument. Something like this:

{
  "isA": "Customer",
  "name": "Superman",
  "age": "2013"
}

'Customer.class' would remain the same and JacksonMain would be modified to:

public class JacksonMain {
    public static void main(String[] args) throws IOException {
        final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
        final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
        final ObjectMapper objectMapper = new ObjectMapper();
        jsonParser.setCodec(objectMapper);

        //Goto the start of the document
        jsonParser.nextToken();


        final JsonNode jsonNode = jsonParser.readValueAsTree();
        final String inputType = jsonNode.get("isA").asText();

        if (inputType.equalsIgnoreCase("Customer")) {
            Object singleCustomer = objectMapper.treeToValue(jsonNode, Customer.class);
            System.out.println(singleCustomer.toString());
        } else if (inputType.equalsIgnoreCase("CustomerDocument")) {
            //Go until the customerList has been reached
            while (!jsonParser.getText().equals("customerList")) {
                jsonParser.nextToken();
            }
            jsonParser.nextToken();

            //Loop through each object within the customerList and deserilize them
            while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                final JsonNode customerNode = jsonParser.readValueAsTree();
                final String eventType = customerNode.get("isA").asText();
                Object event = objectMapper.treeToValue(customerNode, Customer.class);
                System.out.println(event.toString());
            }
        }
    }
}

For a single CUstomer this would produce the following result:

Customer(isA=Customer, name=Superman, age=2013)

For the same code now if I provide the CustomerDocument (the first JSON) then it would not work and fail with error:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because the return value of "com.fasterxml.jackson.core.JsonParser.getText()" is null
    at stackover.JacksonMain.main(JacksonMain.java:32)

I know this issue is happening because of the line

final JsonNode jsonNode = jsonParser.readValueAsTree();

Can someone please explain how to make the code work for both the type of JSON customerDocument and just single Customer using Jackson? I just want to differentiate whether incoming JSON is customerDocument or single Customer. Any help would be really appreciated.

  1. I want to use Jackson to make the differentiation between both the input.
  2. It would be great if there is no need to create any additional classes. However, it's fine if there is a need to create an interface to achieve this.
  3. My CustomerList can be very huge so I am reading one by one so it does not make much memory. hence I do not have the CustomerDocument class with List<Customer> rather I am looking over it and mapping one by one.
BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98

2 Answers2

0

Well you can use Jackson sub type to de-serialize between Customer and CustomerDocument.

Something like following,

public class Main {

    public static void main(String[] args) throws IOException {

        String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"}]}";
//        String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";

        ObjectMapper om = new ObjectMapper();
        BaseResponse baseResponse = om.readValue(s, BaseResponse.class);

        if (baseResponse instanceof CustomerDocument) {
            CustomerDocument cd = (CustomerDocument) baseResponse;
            System.out.println("Inside If..");
            cd.getCustomerList().forEach(System.out::println);
        } else if (baseResponse instanceof Customer) {
            System.out.println("Inside Else If..");
            Customer cs = (Customer) baseResponse;
            System.out.println(cs);;
        }
    }
}


@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Customer.class, name = "Customer"),
        @JsonSubTypes.Type(value = CustomerDocument.class, name = "CustomerDocument")})
interface BaseResponse {}


@Getter
@Setter
@ToString
class Customer implements BaseResponse{
    private String isA;
    private String name;
    private String age;
}

@Getter
@Setter
@ToString
class CustomerDocument implements BaseResponse{
    private String isA;
    private List<Customer> customerList;
}

PS - Uncomment the string in main method to illustrate the other case.

Update

public class Main {

    public static void main(String[] args) throws IOException {

        String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"},{\"isA\":\"Customer B\",\"name\":\"Superman\",\"age\":\"2013\"}]}";
//        String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";

        ObjectMapper om = new ObjectMapper();
        JsonNode node = om.readTree(s);
        String type = node.get("isA").asText();

        if (type.equals("Customer")) {
            Customer c = om.readValue(s, Customer.class);
            System.out.println(c);
        } else if (type.equals("CustomerDocument")) {
            JsonNode nextNode = node.path("customerList");
            List<Customer> cl = om.convertValue(nextNode, new TypeReference<List<Customer>>() {});
            cl.forEach(System.out::println);
        }
    }
}

@Getter
@Setter
@ToString
class Customer {
    private String isA;
    private String name;
    private String age;
}
ray
  • 1,512
  • 4
  • 11
  • 1
    Thanks a lot for the response. I understand how you are achieving the output. But is there any way to make it work without creating the `CustomerDocument` class? Because in my application I am using the standard classes which have been already created hence I am looping over the `CustomerList array` in JSON and assigning it to the `Customer` class. It would be great if there is a way to achieve the output without creating the additional classes. But I am ok with creating the interface. Thanks again. Looking forward to your response. – BATMAN_2008 Sep 02 '21 at 06:07
  • Also, it woule be greate to use the `JsonParser` because my entier application is already built based on it. – BATMAN_2008 Sep 02 '21 at 06:13
  • 1
    Thanks a lot for the update. I was able to get an idea based on your approach I was able to implement it a bit differently. But thanks for the explanation. I have posted the answer below. – BATMAN_2008 Sep 02 '21 at 10:28
  • Try JDDL library which is designed to do just such a thing. https://pretius.com/technologies/json-dynamic-deserialization-library/ https://search.maven.org/artifact/com.pretius/jddl – Dariusz Sep 02 '21 at 10:36
  • @Dariusz Thanks for the response but it does not help me as my whole application is already using the Jackson. – BATMAN_2008 Sep 02 '21 at 11:31
  • JDDL uses jakcson internally; in fact, if you do not configure anything, you get pure jackson deserialization, but with a different API. You can, however, implement a handler on the `isA` field that will customize the behavior for your objects. – Dariusz Sep 02 '21 at 12:57
  • @ray I used the approach mentioned in the https://stackoverflow.com/a/69028555/7584240 but when I am using this approach I am running into an issue, I have posted the question here if you get a chance can you please have a look and provide your response: https://stackoverflow.com/q/69043551/7584240 – BATMAN_2008 Sep 03 '21 at 11:52
  • @Dariusz I am running into an issue while using the Jackson. If you get a chance can you please have a look and provide your response? https://stackoverflow.com/q/69043551/7584240 – BATMAN_2008 Sep 03 '21 at 11:53
0

Following worked for me based on the above provided the answer:

BaseResponse interface:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonSubTypes({
        @JsonSubTypes.Type(value = Customer.class, name = "Customer")})
public interface BaseResponse {
}

Customer class:

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
    private String isA;
    private String name;
    private String age;
}
public class JacksonMain {
    public static void main(String[] args) throws IOException {
        final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
        final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
        final ObjectMapper objectMapper = new ObjectMapper();
        jsonParser.setCodec(objectMapper);

        //Goto the start of the document
        jsonParser.nextToken();

        try {
            BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
            System.out.println("SINGLE EVENT INPUT");
            System.out.println(baseResponse.toString());
        } catch (Exception e) {
            System.out.println("LIST OF CUSTOMER INPUT");
            //Go until the customerList has been reached
            while (!jsonParser.getText().equals("customerList")) {
                jsonParser.nextToken();
            }
            jsonParser.nextToken();

            //Loop through each object within the customerList and deserilize them
            while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                final JsonNode customerNode = jsonParser.readValueAsTree();
                final String eventType = customerNode.get("isA").asText();
                Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
                System.out.println(event.toString());
            }
        }

    }
}
BATMAN_2008
  • 2,788
  • 3
  • 31
  • 98