Jackson 字段排序过程
在使用 Jackson 序列化对象时可以使用 @JsonPropertyOrder 注解、@JsonProperty 注解,MapperFeature 枚举中 SORT_PROPERTIES_ALPHABETICALLY、SORT_CREATOR_PROPERTIES_FIRST 和 SORT_CREATOR_PROPERTIES_BY_DECLARATION_ORDER 枚举值来控制字段的输出顺序。如果没有通过这些方式控制字段的输出顺序,则输出的结果与对象中的字段的定义顺序有关。那么排序的过程是怎样的呢?下面来看一看。
测试用例
我们通过如下的测试用例来演示 Jackson 的排序过程。首先会定义一个实体类 Person,它包含了 @JsonPropertyOrder 注解、@JsonProperty 注解。
1 | import com.fasterxml.jackson.annotation.JsonProperty; |
接下来是测试类。为了保持简单没有为 ObjectMapper 配置 MapperFeature 相关的属性。
1 | import com.fasterxml.jackson.core.JsonProcessingException; |
上面的测试用例将输出如下的结果,为了便于查看,输出的结果经过了格式化。
1 | { |
排序过程
与字段排序有关的方法调用过程如下图所示
sequenceDiagram
autonumber
JacksonTests->>ObjectMapper: writeValueAsString()
ObjectMapper->>DefaultSerializerProvider: serializeValue()
DefaultSerializerProvider->>BeanSerializerFactory: createSerializer()
BeanSerializerFactory->>SerializationConfig: introspect()
SerializationConfig->>BasicClassIntrospector: forSerialization()
BasicClassIntrospector->>BasicClassIntrospector: collectProperties()
BeanSerializerFactory->>BasicBeanDescription: findJsonValueAccessor()
BasicBeanDescription->>POJOPropertiesCollector: getJsonValueAccessor()
POJOPropertiesCollector->>POJOPropertiesCollector: collectAll()
在这个调用过程中我们需要关注第 ⑥ 步和第 ⑨ 步。第 ⑥ 步的 BasicClassIntrospector#collectProperties 方法会创建一个 POJOPropertiesCollector 对象,这个对象将作为第 ⑤ 步创建 BasicBeanDescription 对象的参数。
第 ⑨ 步的 POJOPropertiesCollector#collectAll 方法会
- 调用
POJOPropertiesCollector#_addFields方法,这个方法通过反射的方式把类的字段添加到props变量中。此时props变量中字段的顺序就是在定义类时定义的字段的顺序,如下图中左边灰色部分字段的顺序。 - 调用
POJOPropertiesCollector#_sortProperties方法,这个方法对props变量中的字段进行排序。
_sortProperties 方法对字段的排序分为如下几步
- 从
@JsonPropertyOrder注解中解析alphabetic属性的值,然后解析是否设置了SORT_PROPERTIES_ALPHABETICALLY,根据这两个值来决定是否使用字典排序。 - 看是否有任意一个字段有
@JsonProperty注解且index属性有值,如果是则使用下标排序。 - 定义一个类型为
LinkedHashMap的变量ordered来保存排序的结果。 - 首先,将
@JsonPropertyOrder注解的value属性中明确定义顺序的字段加入ordered中。如上图中红色部分字段的顺序。 - 其次,如果需要使用下标排序,则将所有指定了下标的字段按下标从小到大的顺序加入
ordered中。如上图中黄色部分字段的顺序。 - 然后,如果有创建者字段(即通过构造器设置的字段),同时不使用字典排序(第 1 步)或者配置了
SORT_CREATOR_PROPERTIES_FIRST,则根据是否使用字典排序(第 1 步)和是否配置了SORT_CREATOR_PROPERTIES_BY_DECLARATION_ORDER决定按照字典顺序或按照声明顺序排序创建者字段。在这个例子中不涉及。 - 最后,根据是否使用字典排序(第 1 步)将剩余的字段按字典顺序或则者声明顺序排序后加入
ordered中。如上图绿色部分字段的顺序。
以上就是 Jackson 在序列化是对字段的排序过程。
SpringDoc 字段排序过程
SpringDoc 在底层依赖了 Jackson 并使用上述排序过程。其中的一条调用链路(简化过)如下图所示
sequenceDiagram
autonumber
HTTPClient->>OpenApiWebMvcResource: /v3/api-docs
OpenApiWebMvcResource->>OpenApiResource: openapiJson()
OpenApiResource->>GenericResponseService: build()
GenericResponseService->>SpringDocAnnotationsUtils: extractSchema()
SpringDocAnnotationsUtils->>ModelConverters: resolveAsResolvedSchema()
ModelConverters->>ModelConverterContextImpl: resolve()
ModelConverterContextImpl->>ModelConverter: resolve()
ModelConverter->>ObjectMapper: getSerializationConfig()
ObjectMapper->>DeserializationConfig: introspect()
ModelConverter->>BasicBeanDescription: findJsonValueAccessor()
BasicBeanDescription->>POJOPropertiesCollector: getJsonValueAccessor()
POJOPropertiesCollector->>POJOPropertiesCollector: collectAll()
从调用链路第 ⑧ 步开始的逻辑与 Jackson 序列化对象的逻辑几乎一样,因此我们可以在实体类中配置 @JsonPropertyOrder 或 @JsonProperty 相关的属性值实现对 SpringDoc 字段显示顺序的控制。