当前位置:   article > 正文

fastjson 添加key value_采坑系列—fastjson

fastjson往json串中增加key

e54a0dd498161a61ccfc04f32c95cd47.png

接口协议格式,相信很多人都在用json。而在国内,大多人都用过开源工具fastjson。多对象格式的支持,简单、快速的序列化和反序列化操作,深得广泛应用。不过咖啡最近在使用过程中遇到了一些问题,而且官方也并未完全处理。下面详述一下采坑的过程。

应用背景

众所周知,Json是一种轻量级的数据交换格式,采用一种“键:值”对的文本格式来存储和表示数据,在系统交换数据过程中常常被使用,是一种理想的数据交换语言。所以json就被广泛的应用于接口协议文本格式。
阿里的fastjson工具,开源并且使用方便。
我们常用到的方法:

  • 序列化:JSONObject.toJSONString(obj)/JSONObject.toJSON(obj) 等;
  • 反序列化:JSONObject.parse(str)/JSONObject.parseObject(str)/JSONObject.parseObject(str, Obj.class) 等;
    (obj代表传入的实例对象类型,str为json格式的字符串)

实例分析

一般我们常用JSONObject.toJSONString(obj)直接格式化成json格式字符串,这种序列化能满足通用的场景需求,下游进行反序列化的时候,也能正常解析。但这个方法有一个问题,会直接剔除掉值为null的字段,当下游去解析对象,需要表头的时候,被剔除掉的字段就不会被解析出来。如:

  1. public static void main(String[] args) { //定义map对象
  2. Map<String, Object> param = new HashMap<>();
  3. param.put("id", 123456);
  4. param.put("dcCode", "X00X");
  5. param.put("dcName", null);
  6. String jsonStr1 = JSONObject.toJSONString(param);
  7. System.out.println(jsonStr1);
  8. }
  9. //原对象内容格式化后输出应为:{"id":123456789,"dcCode":"x00X","dcName":null}
  10. //实际输出:{"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行发现了苗头

  1. @SuppressWarnings({ "rawtypes"})public void write(JSONSerializer serializer
  2. , Object object
  3. , Object fieldName
  4. , Type fieldType
  5. , int features //
  6. , boolean unwrapped) throws IOException{
  7. ...210 if (value == null) {211 if (!out.isEnabled(SerializerFeature.WriteMapNullValue)) {212 continue;213 }214 }
  8. ...
  9. }

也就是当你序列化配置枚举类型为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的类型如下:

  1. public static final int WRITE_MAP_NULL_FEATURES
  2. = WriteMapNullValue.getMask()
  3. | WriteNullBooleanAsFalse.getMask()
  4. | WriteNullListAsEmpty.getMask()
  5. | WriteNullNumberAsZero.getMask()
  6. | WriteNullStringAsEmpty.getMask()
  7. ;

是不是看明白了,只有在1.2.29版本及以前版本,才支持这几种空值的序列化处理。
奇葩的事还在后面,即使是处理了这几种类型,最终依然只走到一个方法:

  1. if (value == null) {
  2. out.writeNull(); continue;
  3. }
  4. public void writeNull() {
  5. write("null");
  6. }

也就是即使我通过了处理,最终我还是任性地给你输出一个null。在SerializeWriter类下的writeNull的多态方法中,发现了处理痕迹,却没被MapSerializer调用!

  1. public void writeNull(int beanFeatures , int feature) { if ((beanFeatures & feature) == 0 //
  2. && (this.features & feature) == 0) {
  3. writeNull(); return;
  4. } if (feature == SerializerFeature.WriteNullListAsEmpty.mask) {
  5. write("[]");
  6. } else if (feature == SerializerFeature.WriteNullStringAsEmpty.mask) {
  7. writeString("");
  8. } else if (feature == SerializerFeature.WriteNullBooleanAsFalse.mask) {
  9. write("false");
  10. } else if (feature == SerializerFeature.WriteNullNumberAsZero.mask) {
  11. write('0');
  12. } else {
  13. writeNull();
  14. }
  15. }

最终发现是官方不支持,不知是有意而为之,还是久久得不到修复-_-||
那么,最终我们想要得到我们想要的效果,绝不能止步于此。好在fastjson还提供了一个自定义序列化接口ValueFilter,那就简单了,自定义序列化实现接口:

  1. private static ValueFilter filter = new ValueFilter(){ @Override
  2. public Object process(Object obj, String s, Object v) { if(v==null) return ""; return v;
  3. }
  4. };public static void main(String[] args) {
  5. Map<String, Object> param = new HashMap<>();
  6. param.put("Id", 123456);
  7. param.put("dcCode", "X00X");
  8. param.put("dcName", null);
  9. String jsonStr = JSONObject.toJSONString(param, filter);
  10. System.out.println(jsonStr);
  11. }

执行输出:

{"dcName":"","Id":123456,"dcCode":"X00X"}

至此,fastjson踩坑以及填坑完毕。即使是官方开源框架,我们还是会踩到大坑,只要多思考几步,究其深处,还是会找到填坑的办法的。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/997945
推荐阅读
相关标签
  

闽ICP备14008679号