结合Jackson Streaming API和ObjectMapper来处理JSON

Jackson Streaming API 允许我们解析巨大的JSON文档,而无需一次性将其全部内容加载到内存中。它是处理JSON内容最有效的方法,具有最低的内存和处理开销,但它也有成本: 不是处理JSON内容最方便的方法。

在这篇文章中,我们将看到如何在不失去ObjectMapper提供的强大数据绑定功能的情况下利用Jackson Streaming API。

简介

出于演示的目的,让我们考虑我们想要解析JSON数组,其中每个元素代表一个联系人:

[
  {
    "id": 1,
    "firstName": "John",
    "lastName": "Doe",
    "emails": [
      "john.doe@mail.com"
    ],
    "createdDateTime": "2019-08-19T20:30:00Z"
  },
  {
    "id": 2,
    "firstName": "Jane",
    "lastName": "Poe",
    "emails": [
      "jane.poe@mail.com",
      "janep@mail.com"
    ],
    "createdDateTime": "2019-08-19T20:45:00Z"
  }
]

每个联系人都可以映射到 Contact '的一个实例,定义如下:

@Data
public class Contact {
    private Integer id;
    private String firstName;
    private String lastName;
    private List<String> emails;
    private OffsetDateTime createdDateTime;
}

在大多数应用程序中,我们可以利用ObjectMapper提供的数据绑定功能,并使用以下代码解析数组:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
List<Contact> contacts = mapper.readValue(json, new TypeReference<List<Contact>>() {});

然而,在数组中可能有几百万个元素的情况下,我们可能无法在内存中保存所有数据。所以我们需要回到Jackson Streaming API。

Jackson Streaming API 的灵感来自 StAX,这是一个用于处理 XML 文档的基于事件的 API。 与 StAX 不同,Jackson 使用术语 token 而不是 event,这更好地反映了 JSON 结构。

Jackson Streaming API 的主要类型有:

类型描述
JsonParser用于迭代令牌的逻辑游标,提供低级别 JSON 读取器功能
JsonGenerator低级别 JSON 编写器
JsonFactory用于创建 JsonParserJsonGenerator 实例的工厂

当使用流时,要读(和写)的内容必须按照输入进来(或输出出去)的 完全相同的顺序 进行处理。话虽如此,有必要提到的是,随机访问只由数据绑定(ObjectMapper)树模型(TreeNode) API提供,它们实际上都在底层使用流API来读取和写入JSON文档。

JsonParser

JsonParser 用于将JSON内容及其相关数据解析为令牌。这是Jackson中对JSON内容的最低级别读访问。

为了迭代令牌流,应用程序通过调用nextToken()方法来移动游标。为了访问游标所指向的令牌的数据和属性,应用程序调用一个访问器,该访问器将引用当前所指向的令牌的属性。

JsonParser只跟踪游标当前指向的数据(以及少量用于嵌套的上下文信息、输入行号等)。

使用 JsonParser 解析JSON

让我们看看如何用 JsonParser 解析上面所示的JSON文档:

private void parseJson(InputStream is) throws IOException {
    // 创建一个用于创建JsonParser实例的工厂
    JsonFactory jsonFactory = new JsonFactory();

    // 创建一个JsonParser实例
    try (JsonParser jsonParser = jsonFactory.createParser(is)) {
        // 检查第一个token
        if (jsonParser.nextToken() != JsonToken.START_ARRAY) {
            throw new IllegalStateException("Expected content to be an array");
        }

        // 遍历token,直到数组的末尾
        while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
            // 读取 “Contact” 并对其进行处理
            Contact contact = readContact(jsonParser);
            doSomethingWithContact(contact);
        }
    }
}
private Contact readContact(JsonParser jsonParser) throws IOException {
    // 检查第一个token
    if (jsonParser.currentToken() != JsonToken.START_OBJECT) {
        throw new IllegalStateException("Expected content to be an object");
    }

    Contact contact = new Contact();

    // 遍历对象的属性
    while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
        // 获取当前属性名称
        String property = jsonParser.getCurrentName();

        // 移动到对应的值
        jsonParser.nextToken();

        // 判断每个属性名称并提取值
        switch (property) {
            case "id":
                contact.setId(jsonParser.getIntValue());
                break;
            case "firstName":
                contact.setFirstName(jsonParser.getText());
                break;
            case "lastName":
                contact.setLastName(jsonParser.getText());
                break;
            case "emails":
                List<String> emails = readEmails(jsonParser);
                contact.setEmails(emails);
                break;
            case "createdDateTime":
                contact.setCreatedDateTime(OffsetDateTime.parse(jsonParser.getText()));
                break;
            // 未知属性被忽略
        }
    }

    return contact;
}
private List<String> readEmails(JsonParser jsonParser) throws IOException {
    // 检查第一个token
    if (jsonParser.currentToken() != JsonToken.START_ARRAY) {
        throw new IllegalStateException("Expected content to be an object");
    }

    List<String> emails = new ArrayList<>();

    // 遍历token,直到数组的末尾
    while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
        // 将数组中的每个元素添加到电子邮件列表中
        emails.add(jsonParser.getText());
    }

    return emails;
}

就内存消耗和处理开销而言,这是一种解析JSON内容的 有效 方法。但是,正如我们所看到的,它并不 方便 :它冗长、重复、乏味。

下面我们将看到如何将流与数据绑定结合起来,以减少代码的冗长。

使用 JsonParserObjectMapper 解析JSON

这个例子展示了如何利用 ObjectMapper 的数据绑定功能,同时 流式 传输文件的内容:

private void parseJson(InputStream is) throws IOException {
    // 创建和配置 ObjectMapper 实例
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    // 创建一个 JsonParser 实例
    try (JsonParser jsonParser = mapper.getFactory().createParser(is)) {
        // 检查第一个token
        if (jsonParser.nextToken() != JsonToken.START_ARRAY) {
            throw new IllegalStateException("Expected content to be an array");
        }

        // 遍历标记直到数组结束
        while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
            // 使用 ObjectMapper 读取 Contact 实例并对其进行处理
            Contact contact = mapper.readValue(jsonParser, Contact.class);
            doSomethingWithContact(contact);
        }
    }
}

ObjectMapper 可以直接从 JsonParser 读取值,因此我们可以将流式传输与数据绑定混合,充分利用 [ObjectMapper] 配置,例如模块、反序列化功能和自定义反序列化器。

JsonGenerator

JsonGenerator允许基于对输出JSON令牌的调用序列构造JSON内容。这是Jackson中对JSON内容的最低级别写访问。

使用 JsonGenerator 生成 JSON

让我们看看如何使用 JsonGenerator 生成 JSON 文档:

private void generateJson(List<Contact> contacts, OutputStream os) throws IOException {
    // 创建将用于创建 JsonGenerator 实例的工厂
    JsonFactory jsonFactory = new JsonFactory();

    // 创建一个 JsonGenerator 实例
    try (JsonGenerator jsonGenerator = jsonFactory.createGenerator(os)) {
        // 配置 JsonGenerator 以漂亮地打印输出
        jsonGenerator.useDefaultPrettyPrinter();

        // 写入起始数组标记
        jsonGenerator.writeStartArray();

        // 遍历 Contact 并将每个联系人写入 JSON 对象
        for (Contact contact : contacts) {
            writeContact(jsonGenerator, contact);
        }

        // 写入结束数组标记
        jsonGenerator.writeEndArray();
    }
}
private void writeContact(JsonGenerator jsonGenerator, Contact contact) throws IOException {
    // 写入起始对象token
    jsonGenerator.writeStartObject();

    // 将 Contact 实例的每个字段写成属性/值对
    jsonGenerator.writeNumberField("id", contact.getId());
    jsonGenerator.writeStringField("firstName", contact.getFirstName());
    jsonGenerator.writeStringField("lastName", contact.getLastName());
    jsonGenerator.writeFieldName("emails");
    writeEmails(jsonGenerator, contact.getEmails());
    jsonGenerator.writeStringField("createDateTime", contact.getCreatedDateTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));

    // 写入结束对象token
    jsonGenerator.writeEndObject();
}
private void writeEmails(JsonGenerator jsonGenerator, List<String> emails) throws IOException {
    // 写入开始数组token
    jsonGenerator.writeStartArray();

    // 遍历电子邮件并将每个电子邮件写成字符串
    for (String email: emails) {
        jsonGenerator.writeString(email);
    }

    // 写入结束数组token
    jsonGenerator.writeEndArray();
}

与解析JSON文档的代码一样,它在内存消耗和处理开销方面是 高效的 。但它是冗长和重复的。

那么,让我们看看如何将Jackson Streaming API与数据绑定结合起来以生成JSON文档。

使用 JsonGeneratorObjectMapper 生成JSON

最后,让我们看看如何结合 JsonGeneratorObjectMapper 生成JSON内容:

private void generateJson(List<Contact> contacts, OutputStream os) throws IOException {
    // 创建和配置 ObjectMapper 实例
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    mapper.enable(SerializationFeature.INDENT_OUTPUT);

    // 创建一个 JsonGenerator 实例
    try (JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(os)) {
        // 写入起始数组token
        jsonGenerator.writeStartArray();

        // 遍历 Contact 并将每个联系人写入 JSON 对象
        for (Contact contact : contacts) {
             // 使用 ObjectMapper 将 Contact 实例编写为 JSON
             mapper.writeValue(jsonGenerator, contact);
        }

        // 写入结束数组token
        jsonGenerator.writeEndArray();
    }
}

ObjectMapper 可以直接向 JsonGenerator 写入值,允许我们将流式处理与数据绑定相结合,从而显着减少我们需要编写的代码量。 这种方法利用了 ObjectMapper 中定义的模块、序列化功能和自定义序列化程序。


<<<<<<<<<<<< [完] >>>>>>>>>>>>

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱游泳的老白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值