Jackson 提供三種 JSON 處理模式:
本文聚焦 Streaming API 核心用法與實戰技巧。
JsonFactory factory = new JsonFactory();
// 可選配置
factory.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false); // 手動關閉流
factory.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, true); // 長整數防精度丟失
// 輸出目標:File, OutputStream, Writer
JsonGenerator gen = factory.createJsonGenerator(new File("output.json"), JsonEncoding.UTF8);
// 或者 pretty print
gen.setPrettyPrinter(new DefaultPrettyPrinter());
| 方法 | 用途 | 對應 JSON |
|---|---|---|
writeStartObject() / writeEndObject() | 對象開始/結束 | { ... } |
writeStartArray() / writeEndArray() | 數組開始/結束 | [ ... ] |
writeFieldName(String) | 寫入鍵名 | "name": |
writeStringField(String, String) | 鍵值對快捷寫入 | "k":"v" |
writeNumberField(String, int/long/double) | 數值字段 | "age":21 |
writeBooleanField(String, boolean) | 布爾字段 | "vip":true |
writeNullField(String) | 空值字段 | "k":null |
writeString/Number/Boolean/Null() | 直接寫值(需在數組或根層級) | 值 |
public void writeStudent(JsonGenerator gen) throws IOException {
gen.writeStartObject(); // {
gen.writeStringField("name", "張三"); // "name": "張三"
gen.writeNumberField("age", 20); // "age": 20
gen.writeBooleanField("active", true); // "active": true
gen.writeFieldName("courses"); // "courses":
gen.writeStartArray(); // [
gen.writeString("數學"); // "數學"
gen.writeString("英語"); // "英語"
gen.writeEndArray(); // ]
gen.writeFieldName("address"); // "address":
gen.writeStartObject(); // {
gen.writeStringField("city", "北京"); // "city": "北京"
gen.writeEndObject(); // }
gen.writeEndObject(); // }
}
關鍵點:嚴格遵循「開始-內容-結束」配對,否則拋出 JsonGenerationException。調用 gen.close() 或 gen.flush() 確保緩衝區寫入。
JsonParser.nextToken() 返回下一個 JsonToken 枚舉值:
START_OBJECT ('{'), END_OBJECT ('}'),
START_ARRAY ('['), END_ARRAY (']'),
FIELD_NAME (鍵名), VALUE_STRING, VALUE_NUMBER_INT, VALUE_NUMBER_FLOAT,
VALUE_TRUE, VALUE_FALSE, VALUE_NULL, VALUE_EMBEDDED_OBJECT
JsonParser parser = factory.createJsonParser(new File("input.json"));
while (parser.nextToken() != JsonToken.END_OBJECT) { // 根對象結束停止
JsonToken token = parser.currentToken();
if (token == JsonToken.FIELD_NAME) {
String field = parser.currentName();
parser.nextToken(); // 移動到值
switch (field) {
case "name":
String name = parser.getText(); break;
case "age":
int age = parser.getIntValue(); break;
case "active":
boolean active = parser.getBooleanValue(); break;
case "courses":
List courses = new ArrayList<>();
while (parser.nextToken() != JsonToken.END_ARRAY) {
courses.add(parser.getText());
}
break;
case "address":
// 嵌套對象遞歸或繼續循環
while (parser.nextToken() != JsonToken.END_OBJECT) {
if (parser.currentToken() == JsonToken.FIELD_NAME) {
String subField = parser.currentName();
parser.nextToken();
if ("city".equals(subField)) city = parser.getText();
}
}
break;
}
}
}
if ("hugeArray".equals(field)) {
parser.skipChildren(); // 直接跳過整個數組/對象,極大提升性能
}
// 避免精度丟失
BigDecimal price = parser.getDecimalValue(); // 推薦金額用
long id = parser.getLongValue();
double ratio = parser.getDoubleValue();
大數組中每個元素映射為 POJO,既保持流式內存優勢,又享受自動映射便利:
// JSON: { "users": [ {...}, {...}, ... ] }
parser.nextToken(); // START_OBJECT
while (parser.nextToken() != JsonToken.END_OBJECT) {
if (parser.currentToken() == JsonToken.FIELD_NAME && "users".equals(parser.currentName())) {
parser.nextToken(); // START_ARRAY
ObjectMapper mapper = new ObjectMapper(); // 可復用單例
while (parser.nextToken() != JsonToken.END_ARRAY) {
// 關鍵:readValue 會消費當前 token 並自動定位到下一個元素起始
User user = mapper.readValue(parser, User.class);
processUser(user); // 即時處理,無需全量載入
}
}
}
| 場景 | 建議 |
|---|---|
| 高頻序列化 | JsonFactory、ObjectMapper 為線程安全單例複用 |
| 大量數值 | 開啟 WRITE_NUMBERS_AS_STRINGS 避免 JS 端精度丟失 |
| 網絡傳輸 | 直接寫入 ServletOutputStream,避免中間 ByteArray |
| 異步非阻塞 | 配合 Netty/Servlet 3.1+ AsyncContext,邊解析邊處理 |
| 錯誤恢復 | 捕獲 JsonParseException 記錄位置 parser.getTokenLocation() |
| 維度 | Tree Model | Data Binding | Streaming API |
|---|---|---|---|
| 內存佔用 | O(N) 全樹 | O(N) 對象圖 | O(1) 常數 |
| 編程難度 | 低 | 最低 | 高(手動狀態機) |
| 靈活度 | 最高 | 中(受註解約束) | 高(完全控制) |
| 適用場景 | 小配置、動態結構 | 常規業務 API | 超大文件、高吞吐、嵌入式 |
Jackson Streaming API 是處理海量 JSON 的利器。掌握 JsonGenerator 的「寫入配對」與 JsonParser 的「事件驅動遍歷」,並善用 skipChildren() 与混合模式,即可在內存受限環境下實現極致性能。下次面對 GB 級 JSON 日志、流式 RPC 響應時,請優先考慮它。