接口协议格式,相信很多人都在用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的字段,当下游去解析对象,需要表头的时候,被剔除掉的字段就不会被解析出来。如:

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(){    @Overridepublic 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踩坑以及填坑完毕。即使是官方开源框架,我们还是会踩到大坑,只要多思考几步,究其深处,还是会找到填坑的办法的。

fastjson 添加key value_采坑系列—fastjson相关推荐

  1. fastjson 添加key value_FastJson迁移至Jackson

    FastJson迁移至Jackson 背景 Fastjson在短期内连续爆出高危漏洞, 导致每次都得改代码上线, 已经粉转黑, 决定迁移到SpringBoot自带的Jackson; 相关粉转黑文章参考 ...

  2. fastjson 添加key value_Redis中设置了过期时间的Key,那么你还要知道些什么?

    来源:http://i7q.cn/4NHBun 熟悉Redis的同学应该知道,Redis的每个Key都可以设置一个过期时间,当达到过期时间的时候,这个key就会被自动删除. 1. DEL/SET/GE ...

  3. 小程序采坑系列-this.setData

    先说一下基本使用.官网也有. 比如说你在main.js里面有这些变量.想修改某些值. data: { main_view_bgcolor: "", border: "&q ...

  4. Spark采坑系列(三)Spark操作Hive的坑

    2019独角兽企业重金招聘Python工程师标准>>> 跟着教学试着用Idea编程,实现Spark查询Hive中的表.结果上来就凉了. 捣鼓好久都不行,在网上查有说将hive-sit ...

  5. 采坑系列:Waves MaxxAudio Pro无法正常启动,插入耳机窗口闪过,耳机没声音

    解决: 用驱动精灵下载这个版本的驱动就可以解决 参考网址:http://tieba.baidu.com/p/5887035088 (第11楼,降低声卡驱动版本)

  6. vue2.0怎么渲染html,vue采坑之——vue里面渲染html 并添加样式

    在工作中,有次遇到要把返回的字符串分割成两部分,一部分用另外的样式显示. 这时候,我想通过对得到字符串进行处理,在需要特别样式的字符串片段用html标签(用的span)包裹起来再通过变量绑定就好了.不 ...

  7. 【SpringBoot DB系列】Jooq批量写入采坑记录

    [SpringBoot DB系列]Jooq批量写入采坑记录 前面介绍了jooq的三种批量插入方式,结果最近发现这里面居然还有一个深坑,我以为的批量插入居然不是一次插入多条数据,而是一条一条的插入-,这 ...

  8. 爬坑系列----Redis查询key报空指针异常,而redis中确实存在该key

    爬坑系列----Redis查询key报空指针异常,而redis中确实存在该key 参考文章: (1)爬坑系列----Redis查询key报空指针异常,而redis中确实存在该key (2)https: ...

  9. 【springboot进阶】springboot集成fastjson(三)配置redis使用fastJson进行序列化

    目录 一.引入依赖 二.添加redis连接配置 三.新建RedisConfig类配置 四.测试 五.踩过的坑 数字型的保存 对象的保存 上一章节,我们说到fastjson的自定义序列化和反序列化,这章 ...

  10. 安装docker和jupyter采坑历程

    背景:发现自己电脑配置不够,在进行模型参数调优时基本进行不下去了.计划使用云服务器,由于自己只是用来跑模型.所以按需计费最为合适.考虑到每次使用都需要部署开发环境,索性用docker实现.这样每次部署 ...

最新文章

  1. java中静态方法可以被继承_关于java:静态方法中的继承
  2. SET NOCOUNT ON 作用
  3. 手动安装oracle软件 删软件
  4. Qt Creator建立和运行
  5. SHELL相关的特殊字符总结
  6. 自己写的.Net(C#)代码×××
  7. android 系统(143)---Android实现App版本自动更新
  8. Qt常用数据类型转换
  9. 网上出现反绿坝网站,邀请网友签名以抵制绿坝
  10. 社区发现研究报告——基于信息熵和局部相关性的多标签传播重叠社区发现算法
  11. matlab读取本地图片,matlab读取图片
  12. 在线上传图片二维码识别解析
  13. 软件测试岗位英文自我介绍,软件测试工程师自我介绍_工程师英文自我介绍范文...
  14. 大数据研究,不能“忽悠”
  15. 分享一款windows效率神器,自定义键盘改键(PowerToys)
  16. Leetcode: Longest Valid Parentheses
  17. Asterisk[1]
  18. word基础功能应用:带圈字符如此妙法,好玩有趣
  19. 数据结构889考研真题练习册
  20. 复盘在项目管理中的应用

热门文章

  1. LINUX最好用查看端口占用并杀死(kill)的方式
  2. 浅析StackTrace
  3. 第k小元素——分治法
  4. jenkins vue 打包特别慢_Jenkins 自动化部署
  5. 为什么以太网有最短帧长度的要求_线束工程师:车载以太网介绍
  6. vue el-checkbox循环多个如何选中当前的_vue 快速入门指南(一)
  7. mybatis动态sql,条件判断详解示例
  8. 西门子触摸屏中显示HTML,西门子触摸屏上传问题
  9. android 瀑布流StaggeredGridLayoutManager重新排序后,顶部留白处理
  10. excel 案例素材_盘点 | 十分钟进阶Excel数据可视化