FastJson问题记录
概述
作为一个Java开发者,FastJson可以说是必用的JSON序列化和反序列化工具。
FastJson是一个高性能JSON处理器,无外部依赖。FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要指定引用。FastJson采用独创算法,将parse速度提升到极致,超过所有json库。如果有性能要求,可使用Gson将bean转换json确保数据正确,使用FastJson将Json转换Bean。
Spring MVC/Boot默认使用Jackson来进行model & view的转化,pom.xml
文件加入FastJson的dependency。
此文记录一下工作这么多年遇到的各种问题。
问题
JSONObject与Map互相转换
- Map转换成JSONObject
JSONObject jsonObj = JSONObject.parseObject(JSON.toJSONString(itemMap));
- JSONObject转换成Map
// 方法1
Map<String, Object> map1 = JSONObject.toJavaObject(jsonObj, Map.class);
// 方法2
Map<String, Object> map2 = JSON.parseObject(jsonObj, Map.class);
write javaBean error, fastjson version 1.2.73, class xxx, fieldName : 0
报错的代码片段(其中list是从数据表查询得到的list<po>
数据):
return JSONObject.toJSONString(list);
报错的类xxx,即POJO定义如下(已剔除查询语句未返回的字段,除isactive
外):
public class DashboardCategory {
private String categoryName;
private Boolean isactive;
private String permission;
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName == null ? null : categoryName.trim();
}
public Boolean getIsactive() {
return isactive;
}
public void setIsactive(Boolean isactive) {
this.isactive = isactive;
}
}
显然,这个类里面并没有一个属性字段fieldName是0,报错根源是isactive
,而这个字段的定义不符合规范。
解决方法:
- 将
isactive
重命名为isActive
,改动面较大; - POJO使用lombok @Data注解,并删除getter/setter这种毫无意义的代码;
JSONObject.toJSONString(list, SerializerFeature.IgnoreErrorGetter);
,使用忽视配置。
附:网络上千篇一律抄来抄去的解决方案@JSONField(serialize =false)
根本就不能这样玩,因为使用到这个POJO实体类的接口非常多,前端可能需要展示或使用这个字段,根本就不能无脑地不序列化到responseBody中。
循环引用
经常遇到的一个问题,返回的responseBody带有类似
{
"$ref": "$.data.editorList[0]"
},
这样的脏数据。解决方案:
JSONObject.toJSONString(dataobj, SerializerFeature.DisableCircularReferenceDetect);
ClassCastException
一次项目重构时遇到的问题,测试扔过来的报错日志:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alibaba.fastjson.JSONObject
at com.aaa.cbd.platform.service.facebook.GetAdReports$1.run(GetAdReports.java:1100)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
找到代码行数,设置断点,很快可以拿到一模一样的报错信息:
没道理的啊,重构前没有报错的;最后定位到重构前后使用的fastjson版本不对应,重构前版本为1.2.29,重构后版本为1.2.73。
针对这个ClassCastException,解决方法有两种:
- 降低fastjson版本(推荐)
- 将报错的地方调整为(不推荐,因为不知道有多少地方的代码有类似问题):
JSONObject adConentJson = JSONObject.parseObject(JSONObject.toJSONString(adConentObj));
field ignore
一个常见的开发实践,将接口请求参数和响应结果落库保存,方便问题排查和回溯。接口请求参数或响应结果很多,如果过滤掉不期望落库的数据字段呢?
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
private String getQueryInputStr(JSONObject paramJson) {
// 若干个想要过滤的字段
String[] excludeProperties = {"appkey", "serialNumber"};
PropertyPreFilters filters = new PropertyPreFilters();
PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
excludeFilter.addExcludes(excludeProperties);
return JSONObject.toJSONString(paramJson, excludeFilter);
}
insert fastjson to db
数据表一般定义为下划线,MQ或三方接口responseBody也经常定义为下划线。拿到三方数据时,此时如果直接落库,则无需转化为驼峰命名,也就不需要在Mybatis文件里面定义繁琐的resultMap
,即数据表字段和PO实体类属性映射关系(当然,一般我们可以使用mybatis-generator等工具快速生成该映射关系),然后再引用这个resultMap
:
<resultMap id="BaseResult" type="com.aaa.cbd.platform.po.ChannelTiktokApp">
<result column="advertiser_id" property="advertiserId" jdbcType="VARCHAR"/>
</resultMap>
所以看看Mybatis能不能支持直接insert/update JSONObject数据?
mapper层接口:
void insertBatchChannelTiktokApp(List<JSONObject> list);
mapper.xml文件
简化后片段:
<insert id="insertBatchChannelTiktokApp" useGeneratedKeys="true" keyProperty="id" parameterType="java.util.List">
insert into channel_tiktok_app(partner)
values
<foreach collection="list" item="app" index="index" separator=",">
(#{app.partner})
</foreach>
</insert>
service层代码片段:
JSONArray results = returnVo.getData().getJSONArray("apps");
for (Object obj : results) {
JSONObject jsonObj = (JSONObject) obj;
// jsonObj.put("partner", JSON.toJSONString(jsonObj.getString("partner")));
// jsonObj.put("partner", JSON.toJSONString(jsonObj.get("partner")));
// jsonObj.put("partner", jsonObj.get("partner").toString());
}
List<JSONObject> allList = results.toJavaList(JSONObject.class);
List<JSONObject> insertList = allList.stream().filter(x -> x.getLong("id") == null).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(insertList)) {
channelTiktokAppMapper.insertBatchChannelTiktokApp(insertList);
}
上述代码直接执行,报错信息为(当然,报错信息很大一长串,提取出最有价值的片段):
fix Type handler was null on parameter mapping for property '__frch_params_0.partner'.
It was either not specified and/or could not be found for the javaType (com.alibaba.fastjson.JSONArray) : jdbcType (null) combination.
其中引号内提到partner
字段!通过调试,发现
partner
是一个JSONObject!!解决方法,很简单,就是采用注释的代码片段即可。
其中注释的第二行和第三行等价:
建议使用第2或3行,不要使用第1行。因为第1行相当于对JSON数据做了一层转义,后续如果需要使用该表字段时,如果是前端,还需要反转义2次!
{
title: '第三方合作伙伴',
dataIndex: 'partner',
render: (text) => {
if (text === "" || text == null) {
return null;
}
return JSON.parse(JSON.parse(text)).partner_name;
}
}