Deserializing JSON based on object type
I am creating a service that receives requests in the form of JSON messages. I need to parse the message and take the appropriate action based on the request type. 开发者_Go百科For example (in pseudo code):
switch(request.type) {
case "NewOrder":
createNewOrder(order);
break;
case "CancelOrder"
cancelOrder(orderId);
break;
}
It seems that most JSON APIs (at least those that do the object mapping for you) need the root object type to deserialize. Is there any elegant way around this?
As an example, in the Jackson API (using full object mapping), I need to call the mapper as follows:
NewOrder newOrder = mapper.readValue(src, NewOrder.class);
CancelOrder cancelOrder = mapper.readValue(src. CancelOrder.class);
Which means that I need to know the object's class even before I have parsed it. What I really need is some way to peek into the JSON string, determine the request type 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;
}
Is it possible to do this using Jackson or any other Java JSON parser? I am sure I can go to a lower level and use a streaming API or a node based API, but trying to avoid that complexity if I can.
If you use Jackson to parse the JSON input into a Map, you can quickly access the type information. You can then create the object as the required class and use ObjectMapper.convert
to configure the object from the Map you got 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);
}
}
You could 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);
}
Assuming the Order is just data, delegating responsibility to a DoSomethingService and producing the service via a factory might help:
Service service = takeActionFactory
.buildTheRightServiceForTheValue(src);
service.takeAction();
The factory would 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 the concrete services are sub-classes of Service:
NewOrderService implements Service {
private final NewOrder newOrder;
/**constructor*/
...
void takeAction() {
createNewOrder(newOrder.order);
}
}
Thanks Maurice, Simon and nzroller. Combining ideas from all your responses here's the solution I came up with. Feedback 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 a NewOrder:
JsonMessage jsonMessage =
new JsonMessage(MessageType.NewOrder, newOrder);
ObjectMapper mapper = new ObjectMapper();
String jsonMessageString = mapper.writeValueAsString(jsonMessage);
To deserialize, I first read the JSON string into a tree of JsonNode's. I then read the messageType. Finally, based on the message type, 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;
}
精彩评论