Jackson反序列化使用JsonParser区分直接对象和数组内对象

Jackson deserialization using JsonParser distinguish between the direct object and objects within array

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

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

以下是CustomerDocumentJSON:

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

Customer.class:

@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

现在用户可以提供没有 customerDocument 的直接 customer 对象。像这样:

{
  "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 可能非常大,所以我正在一张一张地阅读,所以不会占用太多内存。因此我没有 CustomerDocument class 和 List<Customer> 而是我正在查看它并一张一张地映射。

你可以使用 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;
}

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

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

客户class:

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

    }
}