目前FastJson采用的是按字母排序,和@JSONField
注解实现自定义排序,是否可以考虑支持一下同toString()
方法一样采用成员变量声明顺序做序列化字段排序 @wenshao
[alibaba/fastjson]【需求】支持按照成员变量声明顺序,做序列化字段排序
回答
关联 Issues
728 #1214 #1224 #1777 #1859 #1995 #2107 #2524 #2594 #2961 #3054@wenshao 望添加一个这样的需求支持,感谢。
/**
* @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的默认排序规则就是:按照成员变量声明顺序做序列化字段排序
把默认的SortField剔除可解决这个问题 JSON.DEFAULT_GENERATE_FEATURE = JSON.DEFAULT_GENERATE_FEATURE &~ SerializerFeature.SortField.getMask();
把默认的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": ""
}
}
@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,具体为什么我还没有具体去研究。抱歉我的疏忽给你带来了困扰。
这样做虽然可以保持顺序,但是不确定会不会带来其他的问题,所以这并不是一个标准的解决方案。
@xuqiming 不太确定是否是版本原因,我本地是1.2.73。 我测试了不管是在HttpMessageConverter中或者之间JSON.toJSONString中,顺序都是我上面描述的那样。
需要按照成员变量声明顺序排序,主要还是因为想使用FastJsonHttpMessageConverter替换MappingJackson2HttpMessageConverter。这样返回给前端的参数顺序是可控的,方便文档抒写与查看。
实际操作使用json并不关心顺序
经过测试。结论如下: 在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);
}
再补充一下,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()
在网上搜了很久都无法解决,最终通过单步跟踪分析代码找到了解决办法,我这里是正确的:
JSON.DEFAULT_GENERATE_FEATURE &= ~SerializerFeature.SortField.getMask();
SerializeConfig serializeConfig = new SerializeConfig(true);
System.out.println(JSON.toJSONString(javaObject, serializeConfig));
@rilyu 感谢提供有效示例,已测试通过。改天对排序与不排序的性能进行测试下
@rilyu 感谢,找了好久
rilyu commented on 27 Jan 2021
100w次测试,性能呈千倍级下降啊