掘金 后端 ( ) • 2024-05-06 11:44

有道无术,术尚可求,有术无道,止于术。

本系列 Jackson 版本 2.17.0

源码地址:https://gitee.com/pearl-organization/study-jaskson-demo

1. 前言

在前几篇文档中,我们介绍了很多特征枚举,实际它们比较偏底层,使用的频率并不高,接下来我们介绍业务处理中需要经常用到了的SerializationFeatureDeserializationFeature

之前说过SerializationFeatureDeserializationFeaturejackson-databind包中提供的枚举,其实现的也是绑定模块中的ConfigFeature接口,可以直接在ObjectMapperJsonMapper对象中配置。

image.png 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进行了缩进处理:

image.png

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 8ZonedDateTime格式来包含时区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.Datejava.sql.Timestampjava.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()方法被正确重写。