[alibaba/fastjson]【需求】支持按照成员变量声明顺序,做序列化字段排序

2024-05-23 187 views
5

目前FastJson采用的是按字母排序,和@JSONField注解实现自定义排序,是否可以考虑支持一下同toString()方法一样采用成员变量声明顺序做序列化字段排序 @wenshao

回答

3

关联 Issues

728 #1214 #1224 #1777 #1859 #1995 #2107 #2524 #2594 #2961 #3054

@wenshao 望添加一个这样的需求支持,感谢。

2
/**
 * @author  ylyue
 * @since   2020年4月5日
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * <p>使用FastJson优先于默认的Jackson做json解析
     * <p>https://github.com/alibaba/fastjson/wiki/%E5%9C%A8-Spring-%E4%B8%AD%E9%9B%86%E6%88%90-Fastjson
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setDateFormat(JSON.DEFFAULT_DATE_FORMAT);
//      config.setFeatures(Feature.OrderedField);
//      config.setFeatures(Feature.SortFeidFastMatch);
        config.setSerializerFeatures(
//              SerializerFeature.SortField,
//              SerializerFeature.MapSortField,
                SerializerFeature.PrettyFormat, 
                SerializerFeature.BrowserCompatible, 
                SerializerFeature.WriteMapNullValue, 
                SerializerFeature.WriteNullBooleanAsFalse,
                SerializerFeature.WriteNullListAsEmpty, 
                SerializerFeature.WriteNullNumberAsZero,
                SerializerFeature.WriteNullStringAsEmpty
        );
        converter.setFastJsonConfig(config);
        converters.add(0, converter);
    }

}

在测试FastJson优先于默认的Jackson做消息转换器时,发现不能实现按照成员变量声明顺序做序列化字段排序,所产生的需求。 Jackson的默认排序规则就是:按照成员变量声明顺序做序列化字段排序

9

把默认的SortField剔除可解决这个问题 JSON.DEFAULT_GENERATE_FEATURE = JSON.DEFAULT_GENERATE_FEATURE &~ SerializerFeature.SortField.getMask();

5

把默认的SortField剔除可解决这个问题 JSON.DEFAULT_GENERATE_FEATURE = JSON.DEFAULT_GENERATE_FEATURE &~ SerializerFeature.SortField.getMask();

感谢你提供的方法,但设置了之后,并非按照声明的成员变量顺序进行排序,好像会变成无规律可循了。

测试代码

声明成员变量如下:

public class FastJsonHttpMessageConverterDTO {

    int inta;
    Integer intb;
    long longa;
    Long longb;
    boolean booleana;
    Boolean booleanb;
    String str;
    Map<?, ?> map;
    Map<?, ?> map2;
    JSONObject jsonObject;
    JSONObject jsonObject2;
    String[] arrayStr;
    long[] arrayLong;
    List<?> list;
    List<?> list2;
    TestEnum testEnum;
    Date date;
    DateTime dateTime;
    LocalDate localDate;
    LocalTime localTime;
    LocalDateTime localDateTime;

}

jackson响应结果:

{
    "code": 200,
    "msg": "成功",
    "flag": true,
    "count": 0,
    "data": {
        "inta": 0,
        "intb": 0,
        "longa": 0,
        "longb": 0,
        "booleana": false,
        "booleanb": null,
        "str": "",
        "map": {},
        "map2": {},
        "jsonObject": {},
        "jsonObject2": {},
        "arrayStr": [],
        "arrayLong": [],
        "list": [],
        "list2": [
            null,
            null,
            "",
            null
        ],
        "testEnum": null,
        "date": null,
        "dateTime": null,
        "localDate": null,
        "localTime": null,
        "localDateTime": null
    }
}

fastjson响应结果

{
    "code": 200,
    "msg": "成功",
    "flag": true,
    "count": null,
    "data": {
        "arrayLong": [],
        "arrayStr": [],
        "booleana": false,
        "booleanb": null,
        "date": null,
        "dateTime": null,
        "inta": 0,
        "intb": null,
        "jsonObject": {},
        "jsonObject2": {},
        "list": [],
        "list2": [
            null,
            null,
            "",
            null
        ],
        "localDate": null,
        "localDateTime": null,
        "localTime": null,
        "longa": 0,
        "longb": null,
        "map": {},
        "map2": {},
        "str": "",
        "testEnum": null
    }
}

设置之后的响应结果:

{
    "code": 200,
    "msg": "成功",
    "flag": true,
    "count": null,
    "data": {
        "map": {},
        "date": null,
        "map2": {},
        "jsonObject2": {},
        "arrayLong": [],
        "list": [],
        "list2": [
            null,
            null,
            "",
            null
        ],
        "jsonObject": {},
        "arrayStr": [],
        "inta": 0,
        "intb": null,
        "booleana": false,
        "longa": 0,
        "longb": null,
        "booleanb": null,
        "localTime": null,
        "testEnum": null,
        "dateTime": null,
        "localDate": null,
        "localDateTime": null,
        "str": ""
    }
}
5

@yl-yue 很抱歉我的回答给你带来了困扰,我引用你的示例代码做了一下测试。

    public static void main(String[] args) {
        FastJsonHttpMessageConverterDTO converterDTO = new FastJsonHttpMessageConverterDTO();

        JSON.DEFAULT_GENERATE_FEATURE = JSON.DEFAULT_GENERATE_FEATURE &~ SerializerFeature.SortField.getMask();
        System.out.println(JSON.toJSONString(converterDTO, SerializerFeature.PrettyFormat,
                SerializerFeature.WriteMapNullValue));

    }

    public static class FastJsonHttpMessageConverterDTO {

        public int inta;
        public Integer intb;
        public long longa;
        public Long longb;
        public boolean booleana;
        public Boolean booleanb;
        public String str;
        public Map<?, ?> map;
        public Map<?, ?> map2;
        public JSONObject jsonObject;
        public JSONObject jsonObject2;
        public String[] arrayStr;
        public long[] arrayLong;
        public List<?> list;
        public List<?> list2;
        public Date date;
        public DateTime dateTime;
        public LocalDate localDate;
        public LocalTime localTime;
        public LocalDateTime localDateTime;

    }

运行的结果

{
    "inta":0,
    "intb":null,
    "longa":0,
    "longb":null,
    "booleana":false,
    "booleanb":null,
    "str":null,
    "map":null,
    "map2":null,
    "jsonObject":null,
    "jsonObject2":null,
    "arrayStr":null,
    "arrayLong":null,
    "list":null,
    "list2":null,
    "date":null,
    "dateTime":null,
    "localDate":null,
    "localTime":null,
    "localDateTime":null
}

是可以按顺序输出的,但里面有一个前提,就是必须加上SerializerFeature.PrettyFormat,具体为什么我还没有具体去研究。抱歉我的疏忽给你带来了困扰。

这样做虽然可以保持顺序,但是不确定会不会带来其他的问题,所以这并不是一个标准的解决方案。

7

@xuqiming 不太确定是否是版本原因,我本地是1.2.73。 我测试了不管是在HttpMessageConverter中或者之间JSON.toJSONString中,顺序都是我上面描述的那样。

需要按照成员变量声明顺序排序,主要还是因为想使用FastJsonHttpMessageConverter替换MappingJackson2HttpMessageConverter。这样返回给前端的参数顺序是可控的,方便文档抒写与查看。

实际操作使用json并不关心顺序

2

经过测试。结论如下: 在gson和jackson 默认都是按字段定义顺序来序列化 fastjson版本1.2.73 如果bean 没有getter/setter, 禁用 SerializerFeature.SortField.getMask() 之后,序列化会按照定义顺序。 但如果 bean 有getter/setter,禁用 SerializerFeature.SortField.getMask() 之后,序列化随机乱序。


没有getter/setter 时,TypeUtils.computeGetters():1858 这里获取不到getter方法,然后到2168行获取fields数组。 这里获取fields数组,和字段定义顺序是一致的。所以序列化出来也会按照定义顺序。 订正: 根据 https://stackoverflow.com/questions/1097807/java-reflection-is-the-order-of-class-fields-and-methods-standardized clazz.getFields() 和clazz.getMethods() 都是无序的。。。 所以这里field和定义顺序一致只是个偶然。

但如果有getter/setter ,代码走到上面这处,根据map遍历结果来决定字段序列化顺序,这导致禁用排序后,每次序列化出来的顺序就是LinkedHashMap的插入顺序。而 这个map插入顺序,是按 clazz.getMethods() 返回来遍历的,而这个返回每次顺序不确定。

然后在 2173行 getFieldInfos() 走到在 com.alibaba.fastjson.util.TypeUtils 的 2197行。就会按照linkedHashMap遍历顺序,生成fieldInfoList.

            for(FieldInfo fieldInfo : fieldInfoMap.values()){
                fieldInfoList.add(fieldInfo);
            }
            if(sorted){
                Collections.sort(fieldInfoList);
            }
2

再补充一下,gson和jackson为什么实现会和字段的定义顺序一致。 基本都是使用了 getDeclaredFields 来作为字段序列化的顺序的

Gson 2.8.6   com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields():152
Field[] fields = raw.getDeclaredFields();

jackson-databind-2.11.3   com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.collectAll():322
- POJOPropertiesCollector._addFields():393
...
- com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector.collect():48
- com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector._findFields():73
cls.getDeclaredFields()
7

在网上搜了很久都无法解决,最终通过单步跟踪分析代码找到了解决办法,我这里是正确的:

JSON.DEFAULT_GENERATE_FEATURE &= ~SerializerFeature.SortField.getMask();
SerializeConfig serializeConfig = new SerializeConfig(true);
System.out.println(JSON.toJSONString(javaObject, serializeConfig));
0

@rilyu 感谢提供有效示例,已测试通过。改天对排序与不排序的性能进行测试下

1

@rilyu 感谢,找了好久