Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deserializing JSON based on object type

Tags:

java

json

jackson

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. 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.

like image 695
Naresh Avatar asked Feb 24 '23 12:02

Naresh


2 Answers

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);
}
}
like image 164
Simon G. Avatar answered Feb 27 '23 02:02

Simon G.


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);
}
like image 37
Maurice Perry Avatar answered Feb 27 '23 01:02

Maurice Perry