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 在需要的地方注入。
// 序列化
mapper.writeValueAsString(user);
mapper.writeValueAsString(userList);
// 反序列化为对象
Foo foo = mapper.readValue(objectStr, Foo.class);
// 反序列化为数组
Foo[] foos = mapper.readValue(arrayStr, Foo[].class);
// 反序列化为基本类型的 List
List<Integer> list = mapper.readValue(arrayStr, List.class);
// 反序列化为复杂类型的 List
List<Foo> list = mapper.readValue(arrayStr,
new TypeReference<List<Foo>>() {
});
// 反序列化为复杂类型的 Map
Map<String, Foo> map = mapper.readValue(objectStr,
new TypeReference<Map<String, Foo>>() {
});
处理泛型时要注意 Java 的类型擦除行为,不要编写这样的代码:
private <T> Wrapper<T> parse() throws IOException {
// do something...
return mapper.readValue(jsonStr,
new TypeReference<Wrapper<T>>() { });
}
在方法设计上传递 com.fasterxml.jackson.core.type.TypeReference
类型的参数:
private <T> Wrapper<T> parseWithTypeReference(
TypeReference<Wrapper<T>> typeReference) throws IOException {
// do something...
return mapper.readValue(jsonStr, typeReference);
用 TreeModel 处理 JSONObject 和 JSONArray
你不应该直接处理 JSONObject 和 JSONArray,除非业务逻辑只需要一个庞大 JSON 中的小部分字段,或者 JSON 的结构需要在运行时推测。此时可借助 TreeModel 将 JSON 转换为 com.fasterxml.jackson.databind.JsonNode
。
String carString = """
{
"color": "Black",
"type": "FIAT",
"specifications": {
"engine": "1.2L",
"horsepower": 69,
"transmission": "Manual"
}
}
""";
JsonNode node = objectMapper.readTree(carString);
assertEquals("Black",
node.get("color").asText());
com.fasterxml.jackson.databind.JsonNode#path(java.lang.String)
方法可按指定路径取值:
assertEquals(69,
node.path("specifications").path("horsepower").asInt());
由于值 / 对象 / 数组都是某种 JsonNode
,在不知道 JSON Schema 的情况下分析结构,可以使用 isValueNode
,isArray
,isObject
等方法辅助判断:
if (column.isArray()) {
for (final JsonNode field : column) {
JsonNode prop = field.get("prop");
if (prop.isValueNode()) {
logger.info("field {} has loaded.", prop.asText());
}
}
}
在 Java 中直接组装 JSONObject 和 JSONArray 也需要借助 TreeModel:
JsonNode node = mapper.valueToTree(fromValue);
// or
JsonNode node = mapper.convertValue(fromValue, JsonNode.class);
// 使用 ObjectMapper
ObjectNode node = mapper.createObjectNode();
ArrayNode nodes = mapper.createArrayNode();
枚举类型
假设你手头有这样一个枚举:
public enum FruitEnum {
BANANA(0, "banana"),
APPLE(1, "apple");
final int code;
final String name;
// getters / setters / ...
}
处理和产生 { "fruit": "BANANA" }
不需要做任何修改:
String jsonStr = "{\"fruit\":\"BANANA\"}";
Basket basket = objectMapper.readValue(jsonStr, Basket.class);
assertEquals(FruitEnum.BANANA, basket.getFruit());
assertEquals(jsonStr, objectMapper.writeValueAsString(basket));
如果你希望在 JSON 中使用 code
属性:
public enum FruitEnum {
BANANA(0, "banana"),
APPLE(1, "apple");
// ...
@JsonValue
public int getCode() {
return code;
}
@JsonCreator
public static FruitEnum getFruit(int code) {
return Stream.of(FruitEnum.values())
.filter(p -> p.getCode() == code)
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
@JsonValue
告诉 Jackson 在序列化时使用这个方法的返回值作为 JSON 属性的值。Jackson 在反序列化创建对象时会选择无参构造函数或 @JsonCreator
修饰的构造函数。
String jsonStr = "{\"fruit\":0}";
Basket basket = objectMapper.readValue(jsonStr, Basket.class);
assertEquals(FruitEnum.BANANA, basket.getFruit());
assertEquals(jsonStr, objectMapper.writeValueAsString(basket));
ObjectMapper 常用配置
尽管 new ObjectMapper()
获得的 ObjectMapper 已经足够开箱即用,仍然可以对其做一些个性化配置。Spring Boot 项目支持在 properties 文件中使用 spring.jackson
修改 ObjectMapper 的部分行为。
你也可以在代码中初始化自己的 ObjectMapper 实例,使用 configure(SerializationFeature f, boolean state)
或 enable/disable(SerializationFeature f)
配置,并通过单例模式透出,或注册为 Bean。
常用序列化配置
// 空对象不抛出异常
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
常用反序列化配置
// 允许 JSON 中存在 Bean 未定义的属性
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
// 允许基本类型字段为空
mapper.disable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
// 空字符串按 null 处理
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// 允许枚举序列化为数字
mapper.disable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
// 声明为容器类型(以 List 为例)的属性,如果 JSON 中对应的属性为空,设置为空容器而不是 null
// forContentNulls is for null array elements
mapper.configOverride(List.class)
.setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY));
// forValueNulls is needed for entire null array
mapper.configOverride(List.class)
.setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
通过 registerModule 扩展功能
支持 java.time 类型
使用 Java 8 提供的 java.time 类型(例如 java.time.LocalDateTime
和 java.time.OffsetDateTime
)时,需要添加 com.fasterxml.jackson.datatype:jackson-datatype-jsr310
依赖并在初始化 ObjectMapper 时注册 JavaTimeModule
。
mapper.registerModule(new 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
,而反序列化能够正确地填充属性。
objectMapper.registerModule(new Jdk8Module());
同样支持 OptionalLong
和 OptionalDouble
。
常用注解与高阶用法
@JsonProperty
修饰成员变量和方法,可在 JSON 字段名与类属性名称不一致时指定 JSON 字段的名称。index
可以指定属性在序列化结果中的顺序。
@JsonProperty
修饰的 private
属性即使没有 getter 和 setter 也能被成功序列化和反序列化。
打包处理未知字段
@JsonAnyGetter
和 @JsonAnySetter
可以把未知或动态变化的字段通过 Map 序列化和反序列化。
public class ChangeableUser {
private long id;
private Map<String, String> extendsProperties = new HashMap<>();
@JsonAnyGetter
public Map<String, String> getExtendsProperties() {
return extendsProperties;
}
@JsonAnySetter
public void addExtendsProperty(String key, String value) {
extendsProperties.put(key, value);
}
}
将 JSON 字符串 {"name":"John Doe","age":24,"phone":"123456789","sex":"male","id":2301}
反序列化为 ChangeableUser 实例,可以在 extendsProperties
中访问 id
之外的属性。
忽略指定属性
@JsonIgnoreProperties
接受一个属性名称的集合,用于屏蔽 Bean 中的属性,使其不会被添加到序列化结果中,也可以使用 @JsonIgnore
注解单独设置每个属性。
@JsonIgnoreProperties({ "id", "password" })
public class SystemUser {
private long id;
private String name;
private String password;
@JsonIgnore
private String address;
}
被 @JsonIgnoreType
修饰的类作为其他类的成员时,无论是序列化还是反序列化都会被忽略。
@JsonAutoDetect
注解可以根据 fieldVisibility
的值和属性是否为 public/protected/private
判断是否要在序列化/反序列化过程中处理。
自定义序列化和反序列化
一些复杂对象在序列化和反序列化过程中需要自行实现转换逻辑,例如:
- 逗号分割的列表与
List<T>
转换 - ISO8601 格式的日期时间字符串与
OffsetDateTime
/LocalDateTime
的转换,后者需要额外处理时区信息,或改为使用DateTimeFormatter.ISO_LOCAL_DATE_TIME
自定义序列化需要继承 StdSerializer<T>
实现 serialize
方法。
@Override
public void serialize(OffsetDateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
}
使用 @JsonSerialize(using = CustomSerializer.class)
修饰属性。
自定义的反序列化操作需要继承 StdDeserializer<T>
实现 deserialize
方法。
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return LocalDateTime.parse(jsonParser.getText(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
使用 @JsonDeserialize(using = CustomDeserializer.class)
修饰属性。
Chaparral
你甚至可以创建自己的 DesensitizeSerializer
以接管反序列化过程。例如笔者编写的 Chaparral - GitHub 通过这种方法实现了使用注解修饰 PII 属性后,令其在反序列化过程中将输出脱敏。
包装与拆箱
@JsonRootName
public record Cargo(String name, BigDecimal weight) {}
// 默认的序列化结果
{"name":"banana","weight":1}
@JsonRootName
注解可使用类名或 value
值作为键值包裹这个结果。
@JsonRootName("_cargo")
public record Cargo(String name, BigDecimal weight) {}
// get
{"_cargo":{"name":"banana","weight":1}}
需要 ObjectMapper 开启 (UN)WRAP_ROOT_VALUE
特性。
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
@JsonUnwrapped
与 @JsonRootName
相反,该注解修饰的属性类可以被 flattened。
public class UnwrappedUser {
public int id;
@JsonUnwrapped
public Name name;
public static class Name {
public String firstName;
public String lastName;
}
}
// get
{"id":1,"firstName":"John","lastName":"Doe"}
多态类型
在一个经典的「宠物商店」场景中,可能会收到这样的请求:
[{
"type": "Dog",
"name": "Buddy",
"breed": "Golden Retriever",
"age": 3,
"favoriteToy": "Tennis Ball"
},
{
"type": "Cat",
"name": "Whiskers",
"breed": "Siamese",
"age": 2,
"indoor": true
},
{
"type": "Bird",
"name": "Tweety",
"species": "Canary",
"age": 1,
"canTalk": false
}]
三种类型的宠物只有 type
,name
和 age
属性是共用的,我们可以用这三个属性设计 Pet
类,在此基础上扩展 Cat
,Dog
和 Bird
子类。
我们希望后续的 Java 逻辑可以分辨对象到底是 Cat.class
,Dog.class
还是 Bird.class
的实例。许多同学选择将 JSON 字符串处理为 JSONObject,get("type")
后或是进行指定类型的反序列化,或是 get()
方法一条路走到黑。
这样看起来有了一些所谓的“灵活性”,不过为了这种灵活性丢失了类型信息,或让代码变得冗余实在是得不偿失。
Jackson 的 JsonTypeInfo
注解可用于处理这种多态场景:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Dog.class, name = "Dog"),
@JsonSubTypes.Type(value = Cat.class, name = "Cat"),
@JsonSubTypes.Type(value = Bird.class, name = "Bird")
})
class Pet {
private String type;
private String name;
private int age;
// ...
}
class Dog extends Pet {
private String breed;
private String favoriteToy;
// ...
List<Pet> pets = objectMapper.readValue(jsonStr,
new TypeReference<>() {
});
assertEquals(Dog.class, pets.get(0).getClass());
assertEquals(Cat.class, pets.get(1).getClass());
assertEquals(Bird.class, pets.get(2).getClass());
使用 @JsonRawValue
输出原始值
在序列化过程中按原始值输出。
public class RawEntity {
@JsonRawValue
private String raw = "{\"name\":\"John Doe\"}";
}
// get
{"raw":{"name":"John Doe"}}
建造者模式
建造者模式可以用来一步步构造复杂的对象,此时对象的序列化和反序列化需要配合使用 @JsonPOJOBuilder
和 @JsonDeserialize
。
@JsonDeserialize(builder = Person.Builder.class)
public class Person {
private final String name;
private final Integer age;
private Person(String name, Integer age) {
this.name = name;
this.age = age;
}
@JsonPOJOBuilder
static class Builder {
String name;
Integer age;
Builder withName(String name) {
this.name = name;
return this;
}
Builder withAge(Integer age) {
this.age = age;
return this;
}
public Person build() {
return new Person(name, age);
}
}
}
如果 Builder
中没有使用 withXXX
和 build
方法赋值和构建对象,需要为 @JsonPOJOBuilde
注解指明 buildMethodName
和 withPrefix
。
@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")
static class Builder {
String name;
Integer age;
Builder setName(String name) {
this.name = name;
return this;
}
Builder setAge(Integer age) {
this.age = age;
return this;
}
public Person create() {
return new Person(name, age);
}
}