首页 > 解决方案 > 由于条件检查,使用 JsonParser 的杰克逊 JSON 反序列化正在跳过第一个键值对

问题描述

我正在使用 Jackson 反序列化 JSON。输入 JSON 可以是 2 种类型CustomerDocumentSingle Customer. CustomerDocument将有一个CustomerList可以由大量的组成,CustomerSingle Customer将只有一个Single Customer。因此,杰克逊必须处理两件事:

  1. 识别提供的 JSON 是否为 a CustomerDocument,如果是则将元素CustomerList逐个反序列化,而不是将整个内容存储到其中List,以减少内存使用量。
  2. 确定提供的 JSON 是否为 a single Customer,如果是,则customer直接反序列化。

我能够实现这一点,并且一切都按预期工作,但是当我提供时,CustomerDocument它无法读取@Context键值对,因为它已经在检查单人时被读取Customer(正如您在第 2 点中提到的那样)。我想下面的代码会让问题变得清晰:

以下是我试图反序列化的 JSON:

{
  "@context": [
    "https://stackoverflow.com",
    {
      "example": "https://example.com"
    }
  ],
  "isA": "CustomerDocument",
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}

以下是我的客户 POJO 类:

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


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

以下是我的Main课程,它将阅读JSON InputStream并决定提供的输入 JSON 是CustomerList还是Single Customer然后相应地反序列化:

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" + baseResponse.toString());
        } catch (Exception e) {
            System.out.println("LIST OF CUSTOMER INPUT");
            //Go until the customerList has been reached
            while (!jsonParser.getText().equals("customerList")) {
                System.out.println("Current Token Name : " + jsonParser.getCurrentName());
                if (jsonParser.getCurrentName() != null && jsonParser.getCurrentName().equalsIgnoreCase("@context")) {
                    System.out.println("WITHIN CONTEXT");
                }
                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());
            }
        }
    }
}

以下是我得到的输出:

LIST OF CUSTOMER INPUT
Current Token Name : isA
Customer(isA=Customer, name=Batman, age=2008)

正如我们所看到的,它只是在打印,Current Token Name: isA我希望它能够打印isA@Context因为它出现在isA. 但我知道它没有打印,因为由于下面的代码行,它已经通过了try

BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);

以下是一个Single CustomerJSON(仅供参考,工作正常):

{
  "@context": [
    "https://stackoverflow.com",
    {
      "example": "https://example.com"
    }
  ],
  "isA": "Customer",
  "name": "Batman",
  "age": "2008"
}

有人可以向我建议如何实现这一目标,是否有更好的解决方法来解决这个问题?

请注意:

  1. CustomerList可以有很多因此Customers我不想将整个存储CustomerList到一些List中,因为它可能需要很多记忆。因此,我正在使用JsonParser,所以我可以一次阅读一个JsonToken

  2. 另外,我不想创建一个CustomerList类,而是想Customer一次读取一个并反序列化它。

  3. JSON 结构无法修改,因为它来自另一个应用程序,它是我的应用程序的标准格式。

标签: javajsonjacksonjackson-databindjackson2

解决方案


我已经修复了代码,我删除了 catch 块。CUSTOMERS_LIST属性区分单个和多个客户。

JacksonMain.java

   public class JacksonMain {

    private static final String CUSTOMERS_LIST = "customerList";

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

        TreeNode treeNode = objectMapper.readTree(jsonParser);
        if (Objects.isNull(treeNode.get(CUSTOMERS_LIST))) {
            BaseResponse baseResponse = objectMapper.treeToValue(treeNode, BaseResponse.class);
            System.out.println("SINGLE EVENT INPUT: " + baseResponse);
        } else {
            System.out.println("LIST OF CUSTOMER INPUT");
            ArrayNode customerList = (ArrayNode) treeNode.get(CUSTOMERS_LIST);
            for (JsonNode node : customerList) {
                BaseResponse event = objectMapper.treeToValue(node, BaseResponse.class);
                System.out.println(event.toString());
            }
        }
    }
}

我创建了两个资源 json 文件

客户.json

{
  "@context": [
    "https://stackoverflow.com",
    {
      "example": "https://example.com"
    }
  ],
  "isA": "Customer",
  "name": "Batman",
  "age": "2008"
}

客户.json

{
  "@context": [
    "https://stackoverflow.com",
    {
      "example": "https://example.com"
    }
  ],
  "isA": "CustomerDocument",
  "customerList": [
    {
      "isA": "Customer",
      "name": "Batman",
      "age": "2008"
    }
  ]
}

客户文档.java

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

客户.java

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

BaseResponse.java

@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")})
@JsonIgnoreProperties(ignoreUnknown = true)
interface BaseResponse {
}

在界面中,我添加了 JsonIgnoreProperties 注释,并添加了一个错过的 JsonSubType。[客户文件]

这两种情况现在都在起作用。

性能测试:

Size: 1,065,065 records / customers handled 
Time in seconds: 30.208

推荐阅读