JSON deserialization based on object type

I am creating a service that receives requests as JSON messages. I need to parse the message and take appropriate action based on the type of request. For example (in pseudo-code):

switch(request.type) { case "NewOrder": createNewOrder(order); break; case "CancelOrder" cancelOrder(orderId); break; } 

It looks like most JSON APIs (at least the ones that perform object matching for you) need a root object type to deserialize. Is there an elegant way around this?

As an example, in the Jackson API (using full object mapping) I need to call mapper as follows:

 NewOrder newOrder = mapper.readValue(src, NewOrder.class); CancelOrder cancelOrder = mapper.readValue(src. CancelOrder.class); 

This means that I need to know the class of the object before I even parsed it. I really need some way to look into the JSON string, determine the type of request, and then call the appropriate readValue () method - something like this:

 String requestType = getRequestType(src); switch(request.type) { case "NewOrder": NewOrder newOrder = mapper.readValue(src, NewOrder.class); createNewOrder(newOrder.order); break; case "CancelOrder" CancelOrder cancelOrder = mapper.readValue(src. CancelOrder.class); cancelOrder(cancelOrder.orderId); break; } 

Can this be done using Jackson or any other Java JSON parser? I am sure that I can go to a lower level and use the streaming API or the node interface API, but try to avoid this complexity if I can.

+4
source share
4 answers

If you use Jackson to analyze JSON input to a map, you can quickly access type information. Then you can create the object as a necessary class and use ObjectMapper.convert to configure the object from the map that you received from Jackson.

Here is an example:

 public class Test1 { private String f; private String b; public void setFoo(String v) { f = v; } public void setBim(String v) { b = v; } public String toString() { return "f=" + f + ", b=" + b; } public static void main(String[] args) throws Exception { String test = "{ \"foo\":\"bar\", \"bim\":\"baz\" }"; ObjectMapper mapper = new ObjectMapper(); HashMap map = mapper.readValue(new StringReader(test), HashMap.class); System.out.println(map); Test1 test1 = mapper.convertValue(map, Test1.class); System.out.println(test1); } } 
+4
source

You can use a wrapper around your order:

 { "NewOrder": {...} } 

or

 { "CancelOrder": {...} } 

UPDATE:

 class Wrapper { newOrder: NewOrder; cancelOrderId: Integer; } Wrapper wrapper = mapper.readValue(src, Wrapper.class); if (wrapper.newOrder != null) { createNewOrder(wrapper.newOrder); } if (wrapper.cancelOrderId != null) { cancelOrder(wrapper.cancelOrderId); } 
+3
source

Assuming the order is just data, delegating responsibility for the DoSomethingService and providing the service with the factory can help:

 Service service = takeActionFactory .buildTheRightServiceForTheValue(src); service.takeAction(); 

factory will parse the JSON object:

 Service buildTheRightServiceForTheValue(src) { switch(request.type) { case "NewOrder": return new NewOrderService(mapper.readValue(src, NewOrder.class)); break; case "CancelOrder" return new CancelOrderService(mapper.readValue(src. CancelOrder.class)); break; } case "SomeOtherObject" return new SomeOtherService(mapper.readValue(src, SomeOtherService.class)); } 

And specific services are subclasses of the Service:

 NewOrderService implements Service { private final NewOrder newOrder; /**constructor*/ ... void takeAction() { createNewOrder(newOrder.order); } } 
0
source

Thanks to Maurice, Simon and nzroller. Combining ideas with all your answers, I found a solution that I came across. Feedback is welcome.

 public enum MessageType { NewOrder, CancelOrder } public class JsonMessage { private MessageType messageType; private Object payload; ... } public class Order { private String orderId; private String itemId; private int quantity; ... } public class NewOrder { private Order order; ... } public class CancelOrder { private String orderId; ... } 

Here's how to serialize NewOrder:

 JsonMessage jsonMessage = new JsonMessage(MessageType.NewOrder, newOrder); ObjectMapper mapper = new ObjectMapper(); String jsonMessageString = mapper.writeValueAsString(jsonMessage); 

For deserialization, I first read the JSON string into the JsonNode tree. Then I read messageType. Finally, depending on the type of message, I read the payload directly as a Java object.

 JsonNode rootNode = mapper.readValue(jsonMessageString, JsonNode.class); MessageType messageType = MessageType.valueOf(rootNode.path("messageType").getTextValue()); switch(messageType) { case NewOrder: NewOrder newOrder = mapper.readValue( rootNode.path("payload"), NewOrder.class); myOrderService.placeOrder(newOrder.getOrder()); break; case CancelOrder: CancelOrder cancelOrder = mapper.readValue( rootNode.path("payload"), CancelOrder.class); myOrderService.cancelOrder(cancelOrder.getOrderId()); break; } 
0
source

All Articles