Jackson反序列化流程分析

Posted on 2025-09-16

概述

Jackson是Java生态老牌的JSON编解码库,它凭借其稳健的代码设计、活跃的社区生态,赢得了一众Java开发者的青睐,成为Spring生态里默认的、开箱即用的JSON编解码库。

img

本文将自顶向下的介绍Jackson是如何完成【string】->【object】转换,帮助大家更好地理解Jackson反序列化的【宏观脉络】

代码架构

随便找一个使用了Jackson的业务服务,可以看到如下相关依赖:

img

拍平的依赖列表无法看不出来代码架构,我们让他立体起来:

暂时无法在飞书文档外展示此内容

  1. 语义化注解

img

  1. 没有业务逻辑,仅仅提供【元信息】。
  2. 用于在DTO的各个语法位置(类上、字段上、方法上)添加【编解码行为提示】

@JsonProperty

主要作用

  1. 属性名映射
    1. 控制 Java 对象字段或方法与 JSON 属性名之间的映射关系
    2. 可以指定不同于 Java 字段名的 JSON 属性名
  2. 访问器标记
    1. 将非静态方法标记为属性的 getter 或 setter
    2. 将非静态字段标记为可序列化/反序列化的属性

基本用法

字段重命名
public class Person {
    @JsonProperty("full_name")
    private String name;
    @JsonProperty("years_old")
    private int age;
}

序列化后 JSON 将是:

{
    "full_name": "John",
    "years_old": 30
}
访问控制

通过 access() 属性控制序列化/反序列化行为:

  • Access.READ_ONLY: 只读,仅序列化不反序列化
  • Access.WRITE_ONLY: 只写,仅反序列化不序列化
  • Access.READ_WRITE: 读写,双向支持
  • Access.AUTO: 自动根据可见性规则判断

@JsonFormat

主要作用

  1. 格式控制:控制各种数据类型的序列化和反序列化格式
  2. 结构形状定义:指定数据在 JSON 中的表现形式(字符串、数字、对象等)
  3. 本地化设置:配置时区、地区等本地化信息
  4. 特性开关:启用或禁用特定的序列化/反序列化特性

基本用法

日期时间格式化
public class Event {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date eventDate;
    
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    private LocalDateTime localDateTime;
}
数字格式化
public class Product {
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private Long id; // 序列化为字符串,避免大数字精度丢失
    
    @JsonFormat(shape = JsonFormat.Shape.NUMBER)
    private Boolean active; // 序列化为数字(0或1)
}
枚举处理
// 序列化为字符串
@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum Status {
    ACTIVE, INACTIVE
}

// 序列化为数字(索引)
@JsonFormat(shape = JsonFormat.Shape.NUMBER)
public enum Priority {
    LOW, MEDIUM, HIGH
}
集合处理
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public class CustomList extends ArrayList<String> {
    // 序列化为对象而不是数组
}
  1. 解析器/生成器

JSON的编解码的本质是完成【string】<–>【object】的双向转换。

在高级数据结构编解码的中间层,需要一个组件来封装 公共的 JSON格式的【字符】读写能力。

  1. Parser:JSON字符串的解析、迭代
  2. Generator:JSON字符串的构造、生成

那么架构就变成了:【string】<–【parser/genetator】–>【object】

这个模式并不特别,都是这么玩的,本质就是 代码分层,各司其职。提升代码可维护性、可拓展性。我们可以对照Hessian2的架构一起看下。

暂时无法在飞书文档外展示此内容

JsonParser解析输入,提供Low Level的【流式】API,供上层ObjectCodec编排。

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;

public class JsonParserExample {
    public static void main(String[] args) throws IOException {
        String jsonString = "{\"name\":\"John\", \"age\":30, \"city\":\"New York\"}";
        
        // 创建 JsonFactory 实例
        JsonFactory factory = new JsonFactory();
        
        // 创建 JsonParser 实例
        try (JsonParser parser = factory.createParser(jsonString)) {
            // 检查当前 token 是否是开始对象
            if (parser.nextToken() == JsonToken.START_OBJECT) {
                // 遍历对象中的所有字段
                while (parser.nextToken() != JsonToken.END_OBJECT) {
                    String fieldName = parser.getCurrentName();
                    parser.nextToken(); // 移动到字段值
                    
                    // 根据字段名处理不同的值
                    switch (fieldName) {
                        case "name":
                            System.out.println("Name: " + parser.getText());
                            break;
                        case "age":
                            System.out.println("Age: " + parser.getIntValue());
                            break;
                        case "city":
                            System.out.println("City: " + parser.getText());
                            break;
                        default:
                            // 跳过未知字段
                            parser.skipChildren();
                            break;
                    }
                }
            }
        }
    }
}
  1. Parser有状态,其持有了底层输入(String/InputStream/File等),每次使用必须要new
  2. Parser的3个核心API是:nextToken/currentToken/readValueAs。
// 使用 nextToken 遍历并处理 JSON
while (parser.nextToken() != null) {
    JsonToken token = parser.getCurrentToken();
    switch (token) {
        case FIELD_NAME:
            String fieldName = parser.getCurrentName();
            break;
        case VALUE_STRING:
            String value = parser.getText();
            break;
        case VALUE_NUMBER_INT:
            int number = parser.getIntValue();
            break;
    }
}
  1. 对象映射

  2. ObjectCodec

imgimg

  1. 对象映射/数据绑定【readValueAs】

imgimg

  1. 伪代码

// JsonParser: 负责低级别的JSON解析,逐个读取token
class JsonParser {
    // 逐个读取JSON token (START_OBJECT, FIELD_NAME, VALUE_STRING等)
    JsonToken nextToken()
    
    // 获取当前token的值
    String getText()
    int getIntValue()
    // ... 其他获取值的方法
    
    // 指向ObjectCodec,用于高级别的对象绑定
    ObjectCodec codec
    
    // 使用codec将当前JSON内容绑定到Java对象
    T readValueAs(Class<T> type) {
        return codec.readValue(this, type)
    }
}

// ObjectCodec: 负责高级别的对象序列化/反序列化
class ObjectCodec {
    // 使用JsonParser将JSON内容绑定到Java对象
    T readValue(JsonParser parser, Class<T> type) {
        // 伪代码逻辑:
        // 1. 使用parser.nextToken()等方法遍历JSON token
        // 2. 根据type信息创建对象实例
        // 3. 将JSON值映射到对象属性
        // 4. 返回填充好的对象
        Object object = createInstance(type)
        while (parser.nextToken() != null) {
            mapTokenToObjectProperty(parser, object)
        }
        return object
    }
    
    // 使用JsonGenerator将Java对象序列化为JSON
    void writeValue(JsonGenerator generator, Object value) {
        // 将对象序列化为JSON输出
    }
}

// 使用示例:
// 1. 创建parser
JsonFactory factory = new JsonFactory()
JsonParser parser = factory.createParser('{"name":"John", "age":30}')

// 2. 设置codec (如ObjectMapper)
parser.setCodec(new ObjectMapper())

// 3. 使用parser和codec一起工作
Person person = parser.readValueAs(Person.class)
// 内部实际调用: parser.getCodec().readValue(parser, Person.class)
  1. 插件拓展

img

ObjectCodec的典型实现是我们日常使用过程中接触最多的ObjectMapper。它承担众多职责之一是【收集自定义配置+拓展】。

拓展的重要一方面是对不同类型/接口的 case by case 的编解码支持,通过 策略模式 实现。

Jackson设计的开闭的拓展发现机制。

常用拓展模块

img

拓展注册API

使用场景不同,需要的拓展不同。

由【上层使用者】来发现和登记,Jackson本身不做【模块发现】。

体现了Jackson 简单、不僭越 的设计哲学。

img

imgimg

  1. 反序列化器维护

img

img

img

  1. 插件发现

上层一般配合JDK ServerLoader机制来做【插件发现】。

imgimg

ObjectMapper

img

img

img

img

img

img

img

img

img

img

JsonFactory

imgimg

ReaderBasedJsonParser

img

imgimg

nextToken

img

ParsingContext

img

压栈

img

弹栈

img

JsonDeserializer

简单示例

img

DTO示例

public class Person {
    private String name;
    private int age;
    private String email;
    
    // 默认构造函数(必需)
    public Person() {}
    
    // 带参数的构造函数
    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
    
    // Getters and Setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
    }
}
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.TextNode;

import java.io.IOException;

public class PersonDeserializer extends JsonDeserializer<Person> {
    
    @Override
    public Person deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException, JsonProcessingException {
        
        // 获取当前节点
        JsonNode node = p.getCodec().readTree(p);
        
        // 从 JSON 节点中提取数据
        String name = getNodeTextValue(node, "name");
        int age = getNodeIntValue(node, "age", 0);
        String email = getNodeTextValue(node, "email");
        
        // 创建并返回 Person 对象
        Person person = new Person();
        person.setName(name);
        person.setAge(age);
        person.setEmail(email);
        
        return person;
    }
    
    // 辅助方法:安全地获取文本值
    private String getNodeTextValue(JsonNode node, String fieldName) {
        JsonNode fieldNode = node.get(fieldName);
        if (fieldNode != null && fieldNode.isTextual()) {
            return fieldNode.asText();
        }
        return null;
    }
    
    // 辅助方法:安全地获取整数值
    private int getNodeIntValue(JsonNode node, String fieldName, int defaultValue) {
        JsonNode fieldNode = node.get(fieldName);
        if (fieldNode != null && fieldNode.isNumber()) {
            return fieldNode.asInt(defaultValue);
        }
        return defaultValue;
    }
}

总结

Jackson真的太复杂了,看得我脑瓜子嗡嗡的。

img