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.
- I want to use Jackson to make the differentiation between both the input.
- 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 aninterface
to achieve this. - 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 theCustomerDocument
class withList<Customer>
rather I am looking over it and mapping one by one.
Advertisement
Answer
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; }