Jackson 的核心由三个模块组成
- Streaming(jackson-core)提供流式解析工具
JsonParser,流式生成工具JsonGenerator - Annotations(jackson-annotations)提供注解
- Databind (jackson-databind) 提供
ObjectMapper用于序列化和反序列化,依赖前两个模块
推荐理由
- 非常 强大灵活的注解和 API
- 通过 Module 模块扩展支持 XML、YAML、Properties 的解析与生成
org.springframework.boot:spring-boot-starter-web默认集成 Jackson,基于 Spring Boot 的 Web 项目可直接使用
基础序列化与反序列化
Jackson 使用 com.fasterxml.jackson.databind.ObjectMapper 实现序列化和反序列化操作。在 Spring 项目中可以使用依赖注入获得 mapper,如果你有自己的配置需求,可以自行创建 Bean 在需要的地方注入。
处理泛型时要注意 Java 的类型擦除行为,不要编写这样的代码:
在方法设计上传递 com.fasterxml.jackson.core.type.TypeReference 类型的参数:
用 TreeModel 处理 JSONObject 和 JSONArray
你不应该直接处理 JSONObject 和 JSONArray,除非业务逻辑只需要一个庞大 JSON 中的小部分字段,或者 JSON 的结构需要在运行时推测。此时可借助 TreeModel 将 JSON 转换为 com.fasterxml.jackson.databind.JsonNode。
com.fasterxml.jackson.databind.JsonNode#path(java.lang.String) 方法可按指定路径取值:
由于值 / 对象 / 数组都是某种 JsonNode,在不知道 JSON Schema 的情况下分析结构,可以使用 isValueNode,isArray,isObject 等方法辅助判断:
在 Java 中直接组装 JSONObject 和 JSONArray 也需要借助 TreeModel:
枚举类型
假设你手头有这样一个枚举:
处理和产生 { "fruit": "BANANA" } 不需要做任何修改:
如果你希望在 JSON 中使用 code 属性:
@JsonValue 告诉 Jackson 在序列化时使用这个方法的返回值作为 JSON 属性的值。Jackson 在反序列化创建对象时会选择无参构造函数或 @JsonCreator 修饰的构造函数。
ObjectMapper 常用配置
尽管 new ObjectMapper() 获得的 ObjectMapper 已经足够开箱即用,仍然可以对其做一些个性化配置。Spring Boot 项目支持在 properties 文件中使用 spring.jackson 修改 ObjectMapper 的部分行为。
你也可以在代码中初始化自己的 ObjectMapper 实例,使用 configure(SerializationFeature f, boolean state) 或 enable/disable(SerializationFeature f) 配置,并通过单例模式透出,或注册为 Bean。
常用序列化配置
常用反序列化配置
通过 registerModule 扩展功能
支持 java.time 类型
使用 Java 8 提供的 java.time 类型(例如 java.time.LocalDateTime 和 java.time.OffsetDateTime)时,需要添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310 依赖并在初始化 ObjectMapper 时注册 JavaTimeModule。
默认配置下 java.time.OffsetDateTime 会被序列化为一个 Unix Timestamp 如1653448397.096875000。java.time.LocalDateTime 则会被序列化为一个数组如 [2022,5,25,11,13,17,96975000]。要取消这一行为(和 Spring MVC 一样处理为类似 ISO-8601 格式的字符串),使用 mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);。
时间对象的序列化与反序列化参考自定义序列化和反序列化部分。
支持 Optional<T>
类成员是 Optional<T> 时,序列化会获得一个 {"present":true},而反序列化会抛出 JsonMappingException 异常。
使用 com.fasterxml.jackson.datatype:jackson-datatype-jdk8 并注册 Jdk8Module,一个空的 Optional<T> 会被映射为 null,而反序列化能够正确地填充属性。
同样支持 OptionalLong 和 OptionalDouble。
常用注解与高阶用法
@JsonProperty
修饰成员变量和方法,可在 JSON 字段名与类属性名称不一致时指定 JSON 字段的名称。index 可以指定属性在序列化结果中的顺序。
@JsonProperty 修饰的 private 属性即使没有 getter 和 setter 也能被成功序列化和反序列化。
打包处理未知字段
@JsonAnyGetter 和 @JsonAnySetter 可以把未知或动态变化的字段通过 Map 序列化和反序列化。
将 JSON 字符串 {"name":"John Doe","age":24,"phone":"123456789","sex":"male","id":2301} 反序列化为 ChangeableUser 实例,可以在 extendsProperties 中访问 id 之外的属性。
忽略指定属性
@JsonIgnoreProperties 接受一个属性名称的集合,用于屏蔽 Bean 中的属性,使其不会被添加到序列化结果中,也可以使用 @JsonIgnore 注解单独设置每个属性。
被 @JsonIgnoreType 修饰的类作为其他类的成员时,无论是序列化还是反序列化都会被忽略。
@JsonAutoDetect 注解可以根据 fieldVisibility 的值和属性是否为 public/protected/private 判断是否要在序列化/反序列化过程中处理。
自定义序列化和反序列化
一些复杂对象在序列化和反序列化过程中需要自行实现转换逻辑,例如:
- 逗号分割的列表与
List<T>转换 - ISO8601 格式的日期时间字符串与
OffsetDateTime/LocalDateTime的转换,后者需要额外处理时区信息,或改为使用DateTimeFormatter.ISO_LOCAL_DATE_TIME
自定义序列化需要继承 StdSerializer<T> 实现 serialize 方法。
使用 @JsonSerialize(using = CustomSerializer.class) 修饰属性。
自定义的反序列化操作需要继承 StdDeserializer<T> 实现 deserialize 方法。
使用 @JsonDeserialize(using = CustomDeserializer.class) 修饰属性。
Chaparral
你甚至可以创建自己的 DesensitizeSerializer 以接管反序列化过程。例如笔者编写的 Chaparral - GitHub 通过这种方法实现了使用注解修饰 PII 属性后,令其在反序列化过程中将输出脱敏。
包装与拆箱
@JsonRootName
当 ObjectMapper 开启 (UN)WRAP_ROOT_VALUE 特性时。
序列化行为默认是这样:
@JsonRootName 注解就相当于类使用的 @JsonProperty:
由于 (UN)WRAP_ROOT_VALUE 特性是全局开启的,
@JsonUnwrapped
与 @JsonRootName 相反,该注解修饰的属性类可以被 flattened。
多态类型
在一个经典的「宠物商店」场景中,可能会收到这样的请求:
三种类型的宠物只有 type,name 和 age 属性是共用的,我们可以用这三个属性设计 Pet 类,在此基础上扩展 Cat,Dog 和 Bird 子类。
我们希望后续的 Java 逻辑可以分辨对象到底是 Cat.class,Dog.class 还是 Bird.class 的实例。许多同学选择将 JSON 字符串处理为 JSONObject,get("type") 后或是进行指定类型的反序列化,或是 get() 方法一条路走到黑。
这样看起来有了一些所谓的“灵活性”,不过为了这种灵活性丢失了类型信息,或让代码变得冗余实在是得不偿失。
Jackson 的 JsonTypeInfo 注解可用于处理这种多态场景:
使用 @JsonRawValue 输出原始值
在序列化过程中按原始值输出。
建造者模式
建造者模式可以用来一步步构造复杂的对象,此时对象的序列化和反序列化需要配合使用 @JsonPOJOBuilder 和 @JsonDeserialize。
如果 Builder 中没有使用 withXXX 和 build 方法赋值和构建对象,需要为 @JsonPOJOBuilde 注解指明 buildMethodName 和 withPrefix。