赞
踩
接口协议格式,相信很多人都在用json。而在国内,大多人都用过开源工具fastjson。多对象格式的支持,简单、快速的序列化和反序列化操作,深得广泛应用。不过咖啡最近在使用过程中遇到了一些问题,而且官方也并未完全处理。下面详述一下采坑的过程。
众所周知,Json是一种轻量级的数据交换格式,采用一种“键:值”对的文本格式来存储和表示数据,在系统交换数据过程中常常被使用,是一种理想的数据交换语言。所以json就被广泛的应用于接口协议文本格式。
阿里的fastjson工具,开源并且使用方便。
我们常用到的方法:
一般我们常用JSONObject.toJSONString(obj)直接格式化成json格式字符串,这种序列化能满足通用的场景需求,下游进行反序列化的时候,也能正常解析。但这个方法有一个问题,会直接剔除掉值为null的字段,当下游去解析对象,需要表头的时候,被剔除掉的字段就不会被解析出来。如:
- public static void main(String[] args) { //定义map对象
- Map<String, Object> param = new HashMap<>();
- param.put("id", 123456);
- param.put("dcCode", "X00X");
- param.put("dcName", null);
- String jsonStr1 = JSONObject.toJSONString(param);
- System.out.println(jsonStr1);
- }
- //原对象内容格式化后输出应为:{"id":123456789,"dcCode":"x00X","dcName":null}
- //实际输出:{"id":123456789,"dcCode":"x00X"}
查阅官方释义后,发现 JSONObject.toJSONString(obj) 这个方法本身并不存在问题,之所以这么做是为了增加格式化的效率,为高性能忽略掉null值的字段格式化是值得的,大部分业务场景下我们也不需要去解析空值字段。但就是某些特定场景下,我们需要再去解析全部表头的时候,是需要准确数据格式的。为此官方也给出了相应解决方案,使用方法:
JSONObject.toJSONString(Object object, SerializerFeature… features)
可以自定义输出配置。 其中com.alibaba.fastjson.serializer.SerializerFeature参数也是fastjson自定义的一个枚举类,部分枚举类型的参考含义如下:
名称含义QuoteFieldNames输出key时是否使用双引号,默认为trueUseSingleQuotes使用单引号而不是双引号,默认为falseWriteMapNullValue是否输出值为null的字段,默认为falseWriteEnumUsingToStringEnum输出name()或者original,默认为falseUseISO8601DateFormatDate使用ISO8601格式输出,默认为falseWriteNullListAsEmptyList字段如果为null,输出为[],而非nullWriteNullStringAsEmpty字符类型字段如果为null,输出为“”,而非nullWriteNullNumberAsZero数值字段如果为null,输出为0,而非null…………
可以看到 WriteMapNullValue,正是我们需要的配置,我们可以把方法改为:JSONObject.toJSONString(Object object, SerializerFeature.WriteMapNullValue)
//输出变为:{"id":123456789,"dcCode":"x00X","dcName":null}
那么现在是不是已经完全达到我们想要的效果了呢?我们在配置参数中又发现一个参数:WriteNullStringAsEmpty(字符类型字段如果为null,输出为“”,而非null) ,比如:当我们需要将展示的数据展示在前台界面,而不需要在前端做任何转换的时候,我们就有必要将控制自动转换为空字符串,这样我们的通用性就更高了。当然我们可以后台手动处理,但如果要一个个地去判空设置,我相信没有多少程序员愿意这么去干。既然官方支持传入动态长度的配置数组,我们是不是将方法改写成为:JSONObject.toJSONString(Object object, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty),可惜事与愿违,输入结果依然为:
{"id":123456789,"dcCode":"x00X","dcName":null}
开始觉得一定是自己用法不对,改为JSONObject.toJSONString(Object object, new SerializerFeature[]{SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty}),结果依然不变。
那只能祭出终极方案:查阅源码。DEBUG层层深入,终于在com.alibaba.fastjson.serializer.MapSerializer类的第211行发现了苗头
- @SuppressWarnings({ "rawtypes"})public void write(JSONSerializer serializer
- , Object object
- , Object fieldName
- , Type fieldType
- , int features //
- , boolean unwrapped) throws IOException{
- ...210 if (value == null) {211 if (!out.isEnabled(SerializerFeature.WriteMapNullValue)) {212 continue;213 }214 }
- ...
- }
也就是当你序列化配置枚举类型为WriteMapNullValue时才生效(out.isEnabled怎么判断的,断点进去即可明白),否则跳过枚举处理。那其余的对null值做处理的序列化配置是用来做装饰的吗?咖啡当前所使用的fastjson版本是fastjson最新版1.2.58,由于不甘心,继续翻阅以前版本的源代码,终于发现了差异,在1.2.29版本同样的方法下发现如下代码(注意第200行):
199 if (value == null) {200 if (!out.isEnabled(SerializerFeature.WRITE_MAP_NULL_FEATURES)) {201 continue;202 }203 }
WRITE_MAP_NULL_FEATURES的类型如下:
- public static final int WRITE_MAP_NULL_FEATURES
- = WriteMapNullValue.getMask()
- | WriteNullBooleanAsFalse.getMask()
- | WriteNullListAsEmpty.getMask()
- | WriteNullNumberAsZero.getMask()
- | WriteNullStringAsEmpty.getMask()
- ;
是不是看明白了,只有在1.2.29版本及以前版本,才支持这几种空值的序列化处理。
奇葩的事还在后面,即使是处理了这几种类型,最终依然只走到一个方法:
- if (value == null) {
- out.writeNull(); continue;
- }
- public void writeNull() {
- write("null");
- }
也就是即使我通过了处理,最终我还是任性地给你输出一个null。在SerializeWriter类下的writeNull的多态方法中,发现了处理痕迹,却没被MapSerializer调用!
- public void writeNull(int beanFeatures , int feature) { if ((beanFeatures & feature) == 0 //
- && (this.features & feature) == 0) {
- writeNull(); return;
- } if (feature == SerializerFeature.WriteNullListAsEmpty.mask) {
- write("[]");
- } else if (feature == SerializerFeature.WriteNullStringAsEmpty.mask) {
- writeString("");
- } else if (feature == SerializerFeature.WriteNullBooleanAsFalse.mask) {
- write("false");
- } else if (feature == SerializerFeature.WriteNullNumberAsZero.mask) {
- write('0');
- } else {
- writeNull();
- }
- }
最终发现是官方不支持,不知是有意而为之,还是久久得不到修复-_-||
那么,最终我们想要得到我们想要的效果,绝不能止步于此。好在fastjson还提供了一个自定义序列化接口ValueFilter,那就简单了,自定义序列化实现接口:
- private static ValueFilter filter = new ValueFilter(){ @Override
- public Object process(Object obj, String s, Object v) { if(v==null) return ""; return v;
- }
- };public static void main(String[] args) {
- Map<String, Object> param = new HashMap<>();
- param.put("Id", 123456);
- param.put("dcCode", "X00X");
- param.put("dcName", null);
- String jsonStr = JSONObject.toJSONString(param, filter);
- System.out.println(jsonStr);
- }
执行输出:
{"dcName":"","Id":123456,"dcCode":"X00X"}
至此,fastjson踩坑以及填坑完毕。即使是官方开源框架,我们还是会踩到大坑,只要多思考几步,究其深处,还是会找到填坑的办法的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。