有道无术,术尚可求,有术无道,止于术。
本系列 Jackson 版本 2.17.0
源码地址:https://gitee.com/pearl-organization/study-jaskson-demo
1. 前言
在前几篇文档中,我们介绍了很多特征枚举,实际它们比较偏底层,使用的频率并不高,接下来我们介绍业务处理中需要经常用到了的SerializationFeature
和DeserializationFeature
。
之前说过SerializationFeature
和DeserializationFeature
是jackson-databind
包中提供的枚举,其实现的也是绑定模块中的ConfigFeature
接口,可以直接在ObjectMapper
或JsonMapper
对象中配置。
SerializationFeature
用于定义了一组影响Java
对象序列化方式的特性。 这些特性既可以通过ObjectMapper
设置(作为默认值),也可以通过ObjectWriter
设置。在第一种情况下,默认值必须遵循先配置后使用的模式(即,一旦定义,不应再更改),所有每次调用的更改都必须使用ObjectWriter
进行。
接下来,我们分类介绍SerializationFeature
提供的所有特征枚举。
2. 通用输出
2.1 WRAP_ROOT_VALUE
WRAP_ROOT_VALUE(false),
WRAP_ROOT_VALUE
用于配置允许在序列化JSON
时包含根级别的包装对象,默认关闭
示例代码:
jsonMapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true);
String jsonStrAA= jsonMapper.writeValueAsString(user);
System.out.println(jsonStrAA);
可以看到输出结果中,JSON
文本最外层包装了一个User
:
{"User":{"id":1767798780627279333,"aByte":0,"aDouble":"NaN","name":"https://www.baidu.com/","age":null,"addr":null,"org":null,"roleList":null}}
2.2 INDENT_OUTPUT
INDENT_OUTPUT(false),
INDENT_OUTPUT
用于配置底层生成器的缩进功能,美化打印方便阅读,默认关闭。
开启后,可以看到JSON
进行了缩进处理:
3. 错误处理
3.1 FAIL_ON_EMPTY_BEANS
FAIL_ON_EMPTY_BEANS(true),
FAIL_ON_EMPTY_BEAN
用于配置序列化一个没有任何属性的 Java Bean
(即空的 POJO
)时是否应该抛出异常,默认关闭。
创建一个不包含任何属性的对象:
public class EmptyBean {
}
执行序列化:
String jsonStrAA = jsonMapper.writeValueAsString(new EmptyBean());
System.out.println(jsonStrAA);
默认配置下,会输出{ }
:
{ }
开启配置后,会报错:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class com.pearl.jacksoncore.demo.feature.EmptyBean and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1330)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:53)
3.2 WRAP_EXCEPTIONS
WRAP_EXCEPTIONS(true),
WRAP_EXCEPTIONS
用于配置在遇到异常时是否应该捕获并包装这些异常,包装异常的目的是为了添加关于问题位置的额外信息(例如,在输入数据中的哪个位置出现了问题)。
3.3 WRITE_SELF_REFERENCES_AS_NULL
WRITE_SELF_REFERENCES_AS_NULL(false),
WRITE_SELF_REFERENCES_AS_NULL
用于配置在遇到对象的自我引用(即对象直接或间接地引用自身)时,将自引用字段的值设置为 null
。
3.4 FAIL_ON_SELF_REFERENCES
FAIL_ON_SELF_REFERENCES
FAIL_ON_SELF_REFERENCES(true),
FAIL_ON_SELF_REFERENCES
用于配置自我引用时,没有启用ObjectId
处理的情况下,抛出一个 JsonMappingException
异常,提示开发者存在自引用问题。
3.5 FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS
FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS(true),
FAIL_ON_UNWRAPPED_TYPE_IDENTIFIERS
用于当遇到未包装的类型标识符时是否应该抛出异常。
4. 输出的生命周期
4.1 CLOSE_CLOSEABLE
CLOSE_CLOSEABLE(false),
CLOSE_CLOSEABLE
用于配置当序列化实现了java.io.Closeable
接口的根级别对象时,是否应在序列化完成后调用其close
方法。
4.2 FLUSH_AFTER_WRITE_VALUE
FLUSH_AFTER_WRITE_VALUE(true),
FLUSH_AFTER_WRITE_VALUE
用于配置是否在每次写入值(即序列化一个属性或值)后都刷新输出流。默认开启,可以确保数据尽早地被写入到输出流中,尤其是在流式处理大量数据时,这对于某些应用可能是有意义的,比如当你想确保即使序列化过程中发生错误,已经写入的数据也不会丢失。
然而该特性通常会降低序列化性能,因为每次写入都需要进行刷新操作,这可能会引入额外的I/O
开销。因此,在不需要即时写入数据的情况下,通常会禁用这个特性以获得更好的性能。
5. 特定数据类型
5.1 WRITE_DATES_AS_TIMESTAMPS
WRITE_DATES_AS_TIMESTAMPS(true),
WRITE_DATES_AS_TIMESTAMPS
用于配置将日期值序列化为数值时间戳,默认开启。
设置一个日期属性:
user.setBirthday(new Date());
String jsonStrAA = jsonMapper.writeValueAsString(user);
System.out.println(jsonStrAA);
输出结果如下:
{
"id" : 1767798780627279333,
"aDouble" : "NaN",
"birthday" : 1712020875375,
"roleList" : null
}
注意:实际应用时,大多都是选择全局的时间格式配置,或使用@JsonFormat
指定格式为yyyy-MM-dd HH:mm:ss
,这样更方便阅读。
5.2 WRITE_DATE_KEYS_AS_TIMESTAMPS
WRITE_DATE_KEYS_AS_TIMESTAMPS(false),
上面的WRITE_DATES_AS_TIMESTAMPS
配置不作用于Map
中日期类型的键,WRITE_DATE_KEYS_AS_TIMESTAMPS
则用于配置Map
中日期类型的键是否序列化为数值时间戳。
添加一个测试Map
:
Map<Date, Object> map = new HashMap<>();
map.put(new Date(), "test");
String jsonStrMap = jsonMapper.writeValueAsString(map);
System.out.println(jsonStrMap);
默认关闭,输出如下:
{
"2024-04-02T01:31:29.464+00:00" : "test"
}
开启后输出如下:
{
"1712021557966" : "test"
}
5.3 WRITE_DATES_WITH_ZONE_ID
WRITE_DATES_WITH_ZONE_ID(false),
WRITE_DATES_WITH_ZONE_ID
用于配置序列化包含时区信息的日期/时间值时,是否应该包含时区ID
。
默认禁用,在序列化过程中不会包含时区ID
,而是使用时区偏移量,如果启用该特性,并且日期/时间值包含时区信息,序列化过程中 将使用Java 8
的ZonedDateTime
格式来包含时区ID
,例如2011-12-03T10:15:30+01:00[Europe/Paris]
。
注意:ISO-8601
规范并未定义包含时区ID
的格式,因此启用此特性可能会导致兼容性问题,如果设置了日期/时间值被序列化为时间戳,则此特性设置无效。
注意:需要引入jackson-datatype-jsr310
模块处理。
5.4 WRITE_DATES_WITH_CONTEXT_TIME_ZONE
WRITE_DATES_WITH_CONTEXT_TIME_ZONE(true),
WRITE_DATES_WITH_CONTEXT_TIME_ZONE
用于配置在序列化包含时区的日期/时间值时是否包含时区或偏移量信息。
注意:需要引入jackson-datatype-jsr310
模块处理。
5.5 WRITE_DURATIONS_AS_TIMESTAMPS
WRITE_DURATIONS_AS_TIMESTAMPS(true),
WRITE_DURATIONS_AS_TIMESTAMPS
用于配置如何序列化 java.time.Duration
对象。当启用这个特性时,Duration
对象将会被序列化为时间戳样式的数值表示(例如,以毫秒或纳秒为单位)。当禁用这个特性时,将会以文本形式(ISO 8601
格式)进行序列化。
注意:需要引入jackson-datatype-jsr310
模块处理。
5.6 WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS
WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS(false),
WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS
用于配置如何序列化字符数组 (char[]
) 。默认禁用,字符数组通常会被序列化为 JSON
字符串,开启时字符数组会被序列化为 JSON
数组。
示例代码:
char[] charArray = {'H', 'e', 'l', 'l', 'o'}; // 创建一个字符数组
String serializedAsJsonArray = jsonMapper.writeValueAsString(charArray); // 序列化字符数组为 JSON 字符串(默认行为)
System.out.println("Serialized as JSON string: " + serializedAsJsonArray);
jsonMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS, true); // 开启 WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS 特性
String serializedAsString = jsonMapper.writeValueAsString(charArray); // 序列化字符数组为 JSON 数组
System.out.println("Serialized as JSON array: " + serializedAsString);
输出结果:
Serialized as JSON string: "Hello"
Serialized as JSON array: [ "H", "e", "l", "l", "o" ]
5.7 WRITE_ENUMS_USING_TO_STRING
WRITE_ENUMS_USING_TO_STRING(false),
WRITE_ENUMS_USING_TO_STRING
用于配置如何序列化枚举对象。
默认禁用,会调用枚举类型的 toString()
方法来获取其字符串表示,并将其作为 JSON
字符串写入。启用时,会使用枚举的名称(即枚举常量的名称)作为 JSON
字符串。
示例代码:
// WRITE_ENUMS_USING_TO_STRING
Color color = Color.RED;
String serializedWithToString = jsonMapper.writeValueAsString(color); // 序列化枚举实例为 JSON 字符串(使用 toString() 的结果 这是默认行为)
System.out.println("Serialized with toString(): " + serializedWithToString);
jsonMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true); // 启用 WRITE_ENUMS_USING_TO_STRING 特性
String serializedWithDefault = jsonMapper.writeValueAsString(color);// 序列化枚举实例为 JSON 字符串(使用枚举常量的名称)
System.out.println("Serialized with default: " + serializedWithDefault);
输出结果:
Serialized with toString(): "RED"
Serialized with default: "FF0000"
5.8 WRITE_ENUMS_USING_INDEX
WRITE_ENUMS_USING_INDEX(false),
WRITE_ENUMS_USING_INDEX
用于配置如何序列化枚举对象。
默认禁用,会调用枚举类型的 toString()
方法来获取其字符串表示,并将其作为 JSON
字符串写入。启用时,不会使用枚举常量的名称或 toString()
方法的结果来序列化枚举,而是使用枚举常量在枚举类型定义中的顺序索引(从 0
开始)。 这在某些特定场景下可能很有用,例如当你需要减少序列化后的大小,或者当枚举常量的名称不是最终序列化形式所期望的。
示例代码:
// WRITE_ENUMS_USING_INDEX
String serializedWithNo = jsonMapper.writeValueAsString(color); // 序列化枚举实例为 JSON 字符串(使用枚举常量的名称,这是默认行为)
System.out.println("Serialized with default: " + serializedWithNo);
jsonMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,true); // 启用 WRITE_ENUMS_USING_INDEX 特性
String serializedWithIndex = jsonMapper.writeValueAsString(color);// 序列化枚举实例为 JSON 字符串(使用枚举常量的索引)
System.out.println("Serialized with index: " + serializedWithIndex);
输出结果:
Serialized with default: "RED"
Serialized with index: 0
说明: 示例中,开启后,RED
的位置是0
,所以输出了0
。
5.9 WRITE_ENUM_KEYS_USING_INDEX
WRITE_ENUM_KEYS_USING_INDEX(false),
WRITE_ENUM_KEYS_USING_INDEX
用于配置序列化时如何处理枚举作为Map
键的集合。
默认禁用,会调用枚举类型的 toString()
方法来获取其字符串表示,并将其作为 JSON
字符串写入。 启用时, 会使用枚举常量在定义中的顺序索引(从 0
开始)作为键的序列化形式,而不是使用枚举常量的名称或 toString()
方法的结果。
示例代码:
// WRITE_ENUM_KEYS_USING_INDEX
Map<Color, String> colorMap = new EnumMap<>(Color.class);
colorMap.put(Color.RED, "Red color");
colorMap.put(Color.GREEN, "Green color");
colorMap.put(Color.BLUE, "Blue color");
String serializedWithColorMap = jsonMapper.writeValueAsString(colorMap); // 序列化包含枚举键的 Map 为 JSON 字符串(使用枚举常量的名称)
System.out.println("Serialized with default: " + serializedWithColorMap);
jsonMapper.configure(SerializationFeature.WRITE_ENUM_KEYS_USING_INDEX ,true); // 启用 WRITE_ENUM_KEYS_USING_INDEX 特性
String serializedWithEnumIndex = jsonMapper.writeValueAsString(colorMap); // 序列化包含枚举键的 Map 为 JSON 字符串
System.out.println("Serialized with index: " + serializedWithEnumIndex);
输出结果:
Serialized with default: {
"RED" : "Red color",
"GREEN" : "Green color",
"BLUE" : "Blue color"
}
Serialized with index: {
"0" : "Red color",
"1" : "Green color",
"2" : "Blue color"
}
5.10 WRITE_NULL_MAP_VALUES
WRITE_NULL_MAP_VALUES
用于配置当Map
值为null
时是否应该序列化这些值。默认开启,Map
中值为null
的值会被序列化。
@Deprecated
WRITE_NULL_MAP_VALUES(true),
从Jackson 2.9
版本开始,建议使用其他更灵活的机制来实现相同的功能,例如使用@JsonInclude
注解或ObjectMapper
的配置覆盖功能。
示例代码:
// 创建一个包含 null 值的 Map
Map<String, String> mapWithNullValue = new HashMap<>();
mapWithNullValue.put("key1", "value1");
mapWithNullValue.put("key2", null); // 这个值为 null
mapWithNullValue.put("key3", "value3");
// 序列化 Map 为 JSON 字符串(包含 null 值,默认行为)
String serializedWithoutNullValues = jsonMapper.writeValueAsString(mapWithNullValue);
System.out.println("Serialized without null values: " + serializedWithoutNullValues);
// 序列化 Map 为 JSON 字符串(不包含 null 值)
jsonMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES ,false); // 关闭 WRITE_NULL_MAP_VALUES 特性
String serializedWithNullValues = jsonMapper.writeValueAsString(mapWithNullValue);
System.out.println("Serialized with null values: " + serializedWithNullValues);
输出结果(当前版本该方式已无效):
Serialized without null values: {
"key1" : "value1",
"key2" : null,
"key3" : "value3"
}
Serialized with null values: {
"key1" : "value1",
"key2" : null,
"key3" : "value3"
}
5.11 WRITE_EMPTY_JSON_ARRAYS
@Deprecated
WRITE_EMPTY_JSON_ARRAYS(true),
WRITE_EMPTY_JSON_ARRAYS
用于配置如何序列化空集合。默认开启,空集合属性会被序列化为空的JSON
数组。
从Jackson 2.8
版本开始,这个特性已经被标记为过时,推荐使用@JsonInclude
注解或配置覆盖功能(当前版本该方式已无效)。
5.12 WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED(false),
WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED
用于配置在序列化时,将只包含一个元素的数组“解包”为单个元素,而不是保留数组结构。
示例代码:
int[] singleElementArray = {1};
String serializedWithList = jsonMapper.writeValueAsString(singleElementArray);
System.out.println(serializedWithList);
jsonMapper.configure(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED ,true);
String serializedWithElementArray = jsonMapper.writeValueAsString(singleElementArray);
System.out.println(serializedWithElementArray);
输出结果:
[ 1 ]
1
5.13 WRITE_BIGDECIMAL_AS_PLAIN
@Deprecated
WRITE_BIGDECIMAL_AS_PLAIN(false),
WRITE_BIGDECIMAL_AS_PLAIN
用于配置否使用java.math.BigDecimal#toPlainString()
方法来序列化 BigDecimal
类型,以防止使用科学记数法来表示值。
从 Jackson 2.5
版本开始,推荐使用 JsonGenerator.Feature#WRITE_BIGDECIMAL_AS_PLAIN
特性来代替。
5.14 WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS(true),
WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS
用于配置如何将 java.util.Date
、java.sql.Timestamp
和 java.time
类型的对象序列化为 JSON
格式。当启用这个特性时,时间戳将以纳秒为单位进行序列化,这提供了更高的时间精度。
注意:需要引入jackson-datatype-jsr310
模块处理。
5.15 ORDER_MAP_ENTRIES_BY_KEYS
ORDER_MAP_ENTRIES_BY_KEYS(false),
ORDER_MAP_ENTRIES_BY_KEYS
用于配置在序列化 Map
对象时是否按照键的自然顺序进行排序。启用这个特性时,Map
中的条目将按照键的字典顺序(即自然顺序)进行序列化,这有助于生成可预测和一致的 JSON
输出。
示例:
Map<String, String> unorderedMap = new HashMap<>(); // 创建一个未排序的 HashMap
unorderedMap.put("c", "value3");
unorderedMap.put("a", "value1");
unorderedMap.put("b", "value2");
jsonMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); // 开启
String jsonUnordered = jsonMapper.writeValueAsString(unorderedMap); // 序列化 HashMap 到 JSON
System.out.println(jsonUnordered); // 输出结果(将按照键的字典顺序排序)
输出结果:
{
"a" : "value1",
"b" : "value2",
"c" : "value3"
}
6. 其他
6.1 EAGER_SERIALIZER_FETCH
EAGER_SERIALIZER_FETCH(true),
EAGER_SERIALIZER_FETCH
用于控制当序列化对象时如何获取序列化器的行为。默认启用,ObjectWriter
在可能的情况下提前获取必要的序列化器,以优化多次使用相同配置的ObjectWriter
实例时的性能。
6.1 USE_EQUALITY_FOR_OBJECT_ID
USE_EQUALITY_FOR_OBJECT_ID(false);
USE_EQUALITY_FOR_OBJECT_ID
允许用户选择在序列化/反序列化过程中如何比较对象的身份。
默认情况下,使用JVM
级别的对象身份比较,如果启用了这个特性,则可以使用equals()
方法进行比较,这在处理与数据库绑定且使用ORM
库的对象时可能很有用。但需要注意的是,当重写equals()
方法时,也必须确保hashCode()
方法被正确重写。