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 | 用于创建 JsonParser 和 JsonGenerator 实例的工厂 |
当使用流时,要读(和写)的内容必须按照输入进来(或输出出去)的 完全相同的顺序 进行处理。话虽如此,有必要提到的是,随机访问只由数据绑定(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内容的 有效 方法。但是,正如我们所看到的,它并不 方便 :它冗长、重复、乏味。
下面我们将看到如何将流与数据绑定结合起来,以减少代码的冗长。
JsonParser
和 ObjectMapper
解析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文档。
JsonGenerator
和 ObjectMapper
生成JSON最后,让我们看看如何结合 JsonGenerator
和 ObjectMapper
生成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
中定义的模块、序列化功能和自定义序列化程序。
<<<<<<<<<<<< [完] >>>>>>>>>>>>