【问题标题】:Deserializing JSON based on object type根据对象类型反序列化 JSON
【发布时间】:2011-04-16 17:23:27
【问题描述】:

我正在创建一个以 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,但如果可以的话,尽量避免这种复杂性。

【问题讨论】:

    标签: java json jackson


    【解决方案1】:

    如果使用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,所以后续问题是 Jackson API 用于创建 MAP - 是他们的流 API 还是树模型 API。另外,如果 Jackson 在创建地图时遇到了麻烦,我可以查看请求类型并将地图的其余部分交给 Jackson 以构造“包装”对象 - 而不是使用 BeanUtils?
    • 太棒了——这应该对我有用!与此同时,我在类似的线路上制定了一个解决方案。将很快发布。唯一的区别是我读的是 JsonNode 的树而不是 HashMap。
    【解决方案2】:

    您可以在订单周围使用包装器:

    {
    "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。
    • 啊,我明白了!所以包装器包装了所有可能的请求(有点像联合),并且(通常)只有其中一个不为空。不错的模式-我必须记住这一点。谢谢莫里斯。
    【解决方案3】:

    假设订单只是数据,将责任委托给 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));
        }
    

    具体的服务是服务的子类:

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

    【讨论】:

    • 我绝对明白你的服务理念(这是命令模式),但问题在于“switch(request.type)”。由于请求类型嵌入在请求本身(即 src)中,因此在 src 被解析(至少部分)之前它不可用。
    • 嗯,好的。在那种情况下,我对杰克逊的了解不够,无法提供帮助。我想知道问题是否出在更高的位置,因此服务的责任太大了。如果您可以使用不同的 URL 或请求参数来区分可能有帮助的不同事件。否则,Maurice Perry 的回答也很有用:JSON 消息中的一些附加类型信息可以传递(类型名称或其他内容),以帮助决定要执行的操作。
    • 在阅读了您的所有回复(您的、Simon 和 Perry 的)后,我有了一些想法。致力于解决方案。当我有合理的事情时,我会发布。
    【解决方案4】:

    感谢 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 的树中。然后我阅读了 messageType。最后,根据消息类型,我直接将payload作为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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-19
      • 1970-01-01
      • 1970-01-01
      • 2016-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多