Skip to content
This repository has been archived by the owner on Oct 23, 2024. It is now read-only.

【需求】支持按照成员变量声明顺序,做序列化字段排序 #3115

Open
yl-yue opened this issue Apr 6, 2020 · 14 comments
Open

Comments

@yl-yue
Copy link
Contributor

yl-yue commented Apr 6, 2020

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

@yl-yue
Copy link
Contributor Author

yl-yue commented Apr 6, 2020

关联 Issues
#728 #1214 #1224 #1777 #1859 #1995 #2107 #2524 #2594 #2961 #3054
@wenshao 望添加一个这样的需求支持,感谢。

@yl-yue
Copy link
Contributor Author

yl-yue commented Apr 6, 2020

/**
 * @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的默认排序规则就是:按照成员变量声明顺序做序列化字段排序

@wenshao
Copy link
Member

wenshao commented May 4, 2020

@xuqiming
Copy link

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

@yl-yue
Copy link
Contributor Author

yl-yue commented Sep 10, 2020

把默认的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": ""
    }
}

@xuqiming
Copy link

xuqiming commented Sep 10, 2020

@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,具体为什么我还没有具体去研究。抱歉我的疏忽给你带来了困扰。

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

@yl-yue
Copy link
Contributor Author

yl-yue commented Sep 14, 2020

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

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

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

@tidus5
Copy link

tidus5 commented Nov 24, 2020

经过测试。结论如下:
在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);
            }

@tidus5
Copy link

tidus5 commented Nov 24, 2020

再补充一下,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()

@rilyu
Copy link

rilyu commented Jan 27, 2021

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

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

@yl-yue
Copy link
Contributor Author

yl-yue commented Jan 29, 2021

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

@panmaze
Copy link

panmaze commented Jul 6, 2022

@rilyu 感谢,找了好久

@yumo1g
Copy link

yumo1g commented Aug 18, 2022

**rilyu ** commented on 27 Jan 2021

100w次测试,性能呈千倍级下降啊

@ArronYR
Copy link

ArronYR commented Oct 22, 2024

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

JSON.DEFAULT_GENERATE_FEATURE &= ~SerializerFeature.SortField.getMask();
SerializeConfig serializeConfig = new SerializeConfig(true);
System.out.println(JSON.toJSONString(javaObject, serializeConfig));
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>2.0.21</version>
</dependency>

2.0.21 按照这个方法也不行

public class Test {

    public static void main(String[] args) {
        B b = new B();
        b.setStart("1");
        b.setEnd("2");
        b.setName("3");

        ArrayList<B> list = new ArrayList<>();
        list.add(b);

        A a = new A();
        a.setList(list);

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

    @Data
    static class B {
        private String start;
        private String end;
        private String name;
    }

    @Data
    static class A {
        private List<B> list;
    }
}

得到的还是

{"list":[{"end":"2","name":"3","start":"1"}]}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants