【问题标题】:Jackson deserialization using JsonParser distinguish between the direct object and objects within arrayJackson反序列化使用JsonParser区分直接对象和数组内的对象
【发布时间】:2021-10-31 13:25:11
【问题描述】:

我正在使用Jackson 对 JSON 进行 Deseilization。 Deseilization 非常适合带有 CustomerDocument 的 JSON。但是,我有一个新要求,我需要确定提供的 JSON 是否具有 CustomerDocument 或只有 Customer

我能够为两者开发逻辑,但问题是当我尝试合并时它不适用于CustomerDocument。我正在寻找一种对两者都适用的解决方案。我想做的就是构建逻辑来区分基于customerDocument 和单个Customer 的传入JSON。

下面是CustomerDocument JSON:

{
  "isA": "CustomerDocument",
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}

客户类:

@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer {
    private String isA;
    private String name;
    private String age;
}

杰克逊主要:

public class JacksonMain {
    public static void main(String[] args) throws IOException {
        final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
        final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
        final ObjectMapper objectMapper = new ObjectMapper();
        jsonParser.setCodec(objectMapper);
        
        //Goto the start of the document
        jsonParser.nextToken();
        
        //Go until the customerList has been reached
        while (!jsonParser.getText().equals("customerList")) {
            jsonParser.nextToken();
        }
        jsonParser.nextToken();

        //Loop through each object within the customerList and deserilize them
        while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
            final JsonNode customerNode = jsonParser.readValueAsTree();
            final String eventType = customerNode.get("isA").asText();
            Object event = objectMapper.treeToValue(customerNode, Customer.class);
            System.out.println(event.toString());
        }
    }
}

上面的代码完美运行并产生以下结果:

Customer(isA=Customer, name=Batman, age=2008)

场景 2

现在用户可以直接提供customer 对象,而无需customerDocument。像这样的:

{
  "isA": "Customer",
  "name": "Superman",
  "age": "2013"
}

'Customer.class' 将保持不变,JacksonMain 将被修改为:

public class JacksonMain {
    public static void main(String[] args) throws IOException {
        final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
        final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
        final ObjectMapper objectMapper = new ObjectMapper();
        jsonParser.setCodec(objectMapper);

        //Goto the start of the document
        jsonParser.nextToken();


        final JsonNode jsonNode = jsonParser.readValueAsTree();
        final String inputType = jsonNode.get("isA").asText();

        if (inputType.equalsIgnoreCase("Customer")) {
            Object singleCustomer = objectMapper.treeToValue(jsonNode, Customer.class);
            System.out.println(singleCustomer.toString());
        } else if (inputType.equalsIgnoreCase("CustomerDocument")) {
            //Go until the customerList has been reached
            while (!jsonParser.getText().equals("customerList")) {
                jsonParser.nextToken();
            }
            jsonParser.nextToken();

            //Loop through each object within the customerList and deserilize them
            while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                final JsonNode customerNode = jsonParser.readValueAsTree();
                final String eventType = customerNode.get("isA").asText();
                Object event = objectMapper.treeToValue(customerNode, Customer.class);
                System.out.println(event.toString());
            }
        }
    }
}

对于单个CUstomer,这将产生以下结果:

Customer(isA=Customer, name=Superman, age=2013)

现在,如果我提供CustomerDocument(第一个 JSON),那么对于相同的代码,它将无法工作并且会因错误而失败:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because the return value of "com.fasterxml.jackson.core.JsonParser.getText()" is null
    at stackover.JacksonMain.main(JacksonMain.java:32)

我知道这个问题是因为这条线而发生的

final JsonNode jsonNode = jsonParser.readValueAsTree();

有人可以解释一下如何使用 Jackson 使代码同时适用于 JSON customerDocument 和单个 Customer 类型吗?我只想区分传入的 JSON 是 customerDocument 还是单个 Customer。任何帮助将不胜感激。

  1. 我想使用 Jackson 来区分两种输入。
  2. 如果不需要创建任何additional classes,那就太好了。但是,如果需要创建 interface 来实现此目的,也可以。
  3. 我的CustomerList 可能非常大,所以我一页一页地阅读,所以它不会占用太多内存。因此我没有 CustomerDocumentList<Customer> 的类,而是我正在查看它并一一映射。

【问题讨论】:

    标签: java jackson json-deserialization jackson-databind jackson2


    【解决方案1】:

    你可以使用 Jackson 子类型在 CustomerCustomerDocument 之间反序列化。

    类似以下,

    public class Main {
    
        public static void main(String[] args) throws IOException {
    
            String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"}]}";
    //        String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";
    
            ObjectMapper om = new ObjectMapper();
            BaseResponse baseResponse = om.readValue(s, BaseResponse.class);
    
            if (baseResponse instanceof CustomerDocument) {
                CustomerDocument cd = (CustomerDocument) baseResponse;
                System.out.println("Inside If..");
                cd.getCustomerList().forEach(System.out::println);
            } else if (baseResponse instanceof Customer) {
                System.out.println("Inside Else If..");
                Customer cs = (Customer) baseResponse;
                System.out.println(cs);;
            }
        }
    }
    
    
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Customer.class, name = "Customer"),
            @JsonSubTypes.Type(value = CustomerDocument.class, name = "CustomerDocument")})
    interface BaseResponse {}
    
    
    @Getter
    @Setter
    @ToString
    class Customer implements BaseResponse{
        private String isA;
        private String name;
        private String age;
    }
    
    @Getter
    @Setter
    @ToString
    class CustomerDocument implements BaseResponse{
        private String isA;
        private List<Customer> customerList;
    }
    

    PS - 取消注释main 方法中的字符串以说明另一种情况。

    更新

    public class Main {
    
        public static void main(String[] args) throws IOException {
    
            String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"},{\"isA\":\"Customer B\",\"name\":\"Superman\",\"age\":\"2013\"}]}";
    //        String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";
    
            ObjectMapper om = new ObjectMapper();
            JsonNode node = om.readTree(s);
            String type = node.get("isA").asText();
    
            if (type.equals("Customer")) {
                Customer c = om.readValue(s, Customer.class);
                System.out.println(c);
            } else if (type.equals("CustomerDocument")) {
                JsonNode nextNode = node.path("customerList");
                List<Customer> cl = om.convertValue(nextNode, new TypeReference<List<Customer>>() {});
                cl.forEach(System.out::println);
            }
        }
    }
    
    @Getter
    @Setter
    @ToString
    class Customer {
        private String isA;
        private String name;
        private String age;
    }
    

    【讨论】:

    • 非常感谢您的回复。我了解您是如何实现输出的。但是有没有办法让它在不创建CustomerDocument 类的情况下工作?因为在我的应用程序中,我使用的是已经创建的标准类,因此我在 JSON 中循环 CustomerList array 并将其分配给 Customer 类。如果有一种方法可以在不创建其他类的情况下实现输出,那就太好了。但我可以创建界面。再次感谢。期待您的回复。
    • 另外,最好使用JsonParser,因为我的整个应用程序已经基于它构建了。
    • 非常感谢您的更新。我能够根据您的方法得到一个想法,我能够以不同的方式实现它。但感谢您的解释。我已经在下面发布了答案。
    • @Dariusz 感谢您的回复,但这对我没有帮助,因为我的整个应用程序已经在使用 Jackson。
    【解决方案2】:

    根据以上内容为我工作提供了答案:

    BaseResponse 接口:

    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Customer.class, name = "Customer")})
    public interface BaseResponse {
    }
    

    客户类别:

    @Data
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class Customer implements BaseResponse {
        private String isA;
        private String name;
        private String age;
    }
    
    public class JacksonMain {
        public static void main(String[] args) throws IOException {
            final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
            final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
            final ObjectMapper objectMapper = new ObjectMapper();
            jsonParser.setCodec(objectMapper);
    
            //Goto the start of the document
            jsonParser.nextToken();
    
            try {
                BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
                System.out.println("SINGLE EVENT INPUT");
                System.out.println(baseResponse.toString());
            } catch (Exception e) {
                System.out.println("LIST OF CUSTOMER INPUT");
                //Go until the customerList has been reached
                while (!jsonParser.getText().equals("customerList")) {
                    jsonParser.nextToken();
                }
                jsonParser.nextToken();
    
                //Loop through each object within the customerList and deserilize them
                while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                    final JsonNode customerNode = jsonParser.readValueAsTree();
                    final String eventType = customerNode.get("isA").asText();
                    Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
                    System.out.println(event.toString());
                }
            }
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-07
      • 2021-10-05
      • 2011-09-15
      • 1970-01-01
      • 2019-02-07
      • 2014-02-12
      相关资源
      最近更新 更多