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互相转换

  1. Map转换成JSONObject
JSONObject jsonObj = JSONObject.parseObject(JSON.toJSONString(itemMap));
  1. 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,而这个字段的定义不符合规范。
解决方法:

  1. isactive重命名为isActive,改动面较大;
  2. POJO使用lombok @Data注解,并删除getter/setter这种毫无意义的代码;
  3. 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,解决方法有两种:

  1. 降低fastjson版本(推荐)
  2. 将报错的地方调整为(不推荐,因为不知道有多少地方的代码有类似问题):
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;
    }
}

参考

posted @ 2021-09-04 16:45  johnny233  阅读(82)  评论(0编辑  收藏  举报  来源