根据对象类型反序列化JSON

4

我正在创建一个接收JSON消息请求的服务。我需要解析这些消息并根据请求类型采取适当的行动。例如(伪代码):

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

看起来大多数JSON API(至少那些为您执行对象映射的API)需要根对象类型进行反序列化。有没有什么优雅的方法可以避免这种情况?

例如,在Jackson API中(使用完整对象映射),我需要按以下方式调用映射器:

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

这意味着我需要在解析之前知道对象的类。我真正需要的是一种方法来查看JSON字符串,确定请求类型,然后调用适当的readValue()方法 - 就像这样:

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;
}

使用Jackson或其他Java JSON解析器是否可以实现这一点?我相信我可以使用流式API或基于节点的API来进行更低级别的操作,但如果可能的话,尽量避免这种复杂性。

4个回答

5
如果您使用Jackson将JSON输入解析为Map,您可以快速访问类型信息。然后,您可以创建所需类的对象,并使用ObjectMapper.convert从Jackson获得的Map配置对象。
这是一个例子:
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);
}
}

刚开始使用Jackson,所以接下来的问题是,用于创建MAP的Jackson API是流API还是树模型API。此外,如果Jackson已经创建了Map,我能否查看请求类型并将Map的其余部分交给Jackson构造“包装”对象,而不是使用BeanUtils? - Naresh
太棒了 - 这对我应该有效!与此同时,我已经找到了一个类似的解决方案。很快就会发布。唯一的区别是我读取了JsonNode树而不是HashMap。 - Naresh

3
您可以使用一个包装器来包含您的订单:
{
"NewOrder": {...}
}

或者

{
"CancelOrder": {...}
}

更新:

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);
}

包装器如何帮助我解决这个问题?请注意,NewOrder请求的内容是完整的订单,而CancelOrder请求的内容只是订单ID。一般来说,请求的内容可能非常不同。例如,另一个请求可能是SubmitUserProfile,其中包含用户配置文件。 - Naresh
啊,我明白了!所以包装器包装了所有可能的请求(有点像联合),通常只有一个不为空。很好的模式 - 我得记住这个。谢谢Maurice。 - Naresh

0
假设订单只是数据,将责任委托给DoSomethingService并通过工厂生成服务可能会有所帮助:
Service service = takeActionFactory
    .buildTheRightServiceForTheValue(src);
service.takeAction();

工厂将解析JSON对象:

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));
    }

具体的服务是 Service 的子类:

NewOrderService implements Service {
    private final NewOrder newOrder;
    /**constructor*/
    ...

    void takeAction() {
        createNewOrder(newOrder.order);
    }
}

我完全理解你的服务想法(这是命令模式),但问题在于“switch(request.type)”语句。由于请求类型嵌入到请求本身中(即src),因此在src被解析之前(至少部分解析)它是不可用的。 - Naresh
好的,那我就不了解Jackson足够帮忙了。不过我有点疑惑,可能问题出在更高的地方,因此服务承担了过多的责任。如果你可以使用不同的URL或请求参数来区分不同的事件,那可能会有所帮助。否则,Maurice Perry的答案也很有用:可以在JSON消息中传递一些附加类型信息(类型名称或其他信息),以帮助决定要执行的操作。 - nzroller
在阅读了你们所有人的回复(包括你的、Simon的和Perry的)后,我有几个想法。正在努力解决问题。有合理的方案时我会发布的。 - Naresh

0

感谢Maurice、Simon和nzroller。结合你们所有回复的想法,这是我想出的解决方案。欢迎反馈。

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

以下是如何序列化NewOrder:

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

为了反序列化,我首先将JSON字符串读入到JsonNode的树中。然后读取消息类型。最后,根据消息类型,我直接将有效载荷读取为Java对象。
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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接