JSON
tio-utils 的 json 模块以抽象类 Json 为核心,方便扩展第三方实现,tio 官方给出了三个 Json 实现,分别是 TioJson、FastJson2、Jackson,这三个实现继承自抽象类 Json。
抽象类 Json 的核心抽象如下:
public abstract class Json {
public abstract String toJson(Object object);
public abstract <T> T parse(String jsonString, Class <T> type);
}
如上代码可以看出 Json 抽象就是 Object 与 json string 互转的两个方法,toJson(...)将任意 java 类型转成 json string,而 parse 将 json string 再反向转成范型指定的对象。
Json 配置
tio 官方提供了 Json 抽象类的三个实现:TioJson、FastJson、Jackson,如果不进行配置,那么默认使用 TioJson 实现,指定为其它实现需要在 configConstant 进行如下配置:
public void configConstant(Constants me) {
me.setJsonFactory(new FastJsonFactory());
}
上面配置将系统默认使用的 TioJson 切换到了 FastJson。还可以通过扩展 Json 抽象类以及 JsonFactory 来实现定制的 Json 实现。
假定用户扩展出了一个 MyJson 与 MyJsonFactory ,那么可以通过如下的方式切换到自己的实现上去:
public void configConstant(Constants me) {
me.setJsonFactory(new MyJsonFactory());
}
此外,tio 官方还提供了 MixedJson、MixedJsonFactory 实现,这个实现让转 json string 时使用 TioJson,反向转成对象则使用 FastJson。 如果希望在非 web 下进行配置,需要使用 JsonManager,例如:
JsonManager.me().setDefaultJsonFactory(new MixedJsonFactory());
还可以配置 Date 类型转 json 后的格式:
public void configConstant(Constants me) {
me.setJsonDatePattern("yyyy-MM-dd");
}
注意,在使用 MixedJsonFactory、FastJsonFactory、JacksonFactory 时需要添加其依赖,具体依赖参考下一小节内容。
Json 的四个实现
final 官方默认给出了四种 json 实现:TioJson、FastJson、Jackson、MixedJson,可以满足绝大多数需求。
1、TioJson
TioJson 是 tio 官方最早的一个实现,这个实现最重要一点就是在转换 tio 的 Model 时是先获取 Model 中的 Map attrs 属性,然后再去转换这个 Map 对象。即便的 Model 生成了 getter 方法,也不会被转换时调用。
针对 Model.attrs 属性进行转换而不是利用 getter 方法进行转换有如下几个原因:
A:支持多表关联查询结果的转换
无论是 Model 还是传统 Java Bean,其 getter 方法都是固定的,而多表关联查询的 sql 语句中的 select 中的字段是动态的,通常还包含关联表中的字段,而这些字段值没有相关的 getter 方法,这些字段就无法被转换
B:早期的 tio 用户没有为 Model 生成 getter 方法
注意:TioJson 只支持对象转 json string,不支持反向的 json string 转对象,可以通过使用 MixedJson 来支持反向转换:me.setJsonFactory(new MixedJsonFactory()); tio 4.9 版本对 TioJson 进行了彻底的重构与优化,新增了一些功能。
A、Model、Row 字段名转换为驼峰格式 TioJson.setModelAndRecordFieldNameToCamelCase(); 大量开发者将数据库字段名命名成下划线的格式,如:"user_id",这就造成了与 java 变量名风格的不统一,对代码质量有一定损害。
上述配置只有在碰到下划线时才会对其进行转换,否则原样保留,而 oracle 用户习惯使用大写的字段名,所以需要使用如下配置:
TioJson.setModelAndRecordFieldNameConverter(fieldName -> {
return StrKit.toCamelCase(fieldName, true);
});
上述代码中的第二个参数 true 表示将字段名转成小写字母,而无论是否出现下划线。
B、新增 setSkipNullValueField 配置方法 该方法用于去除 null 值字段的转换:
TioJson.setSkipNullValueField(true);
C、新增 addToJson 扩展方法 该方法可以细粒度地对任意类型的转换进行扩展:
TioJson.addToJson(Timestamp.class, (value, depth, ret) -> {
ret.addLong(((Timestamp)value).getTime());
});
以上扩展代码,将 Timestamp 类型转换成 long 值。
2、FastJson
FastJson 是对第三方的 fastjson 进行的二次封装,该实现最重要的一点就是转换依赖于 Model、java bean 的 getter 方法。使用 fastjson 可以按照其官方文档去配置 fastjson 的各种转换参数。
使用 FastJson 封装时,需要添加其依赖:
<fastjson2.version>2.0.52</fastjson2.version>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
### 3、Jackson
该实现与 FastJson 类似,是对第三方的 jackson 的二次封装
使用 Jackson 封装时,需要添加其依赖:
4、MixedJson
MixedJson 是对 TioJson、FastJson 的再一次封装,Object 转 json string 时使用 TioJson 的实现,而反向 json string 转 Object 使用 FastJson。
这个实现结合了 TioJson 与 FastJson 两者的优势。 前者不支持 json string 到 Object 的转换,后者不支持关联表 sql 查询动态字段的转换。
使用 MixedJson 封装时需要添加 FastJson 封装的依赖:
配置Json实现
默认的json实现是MixedJsonFactory,可以通过app.json.provider配置json实现,支撑fastjson,gson,jackson,tio
tio.json.provider=fastjson
Json 转换用法
json 转换在 tio 中的使用分为两类用法,第一类是使用配置的 json 转换,第二类是指定某个实现进行 json 转换。
1、使用JsonUtils
import nexus.io.tio.utils.json.JsonUtils;
JsonUtils.toJson(object);
JsonUtils.toSkipNullJson(object);
JsonUtils.toJsonBytes(object);
JsonUtils.parse(responseText,xxx.class);
3、使用配置的 json 实现转换
如下代码将使用前面章节中介绍的配置的 json 实现进行转的换:
// 使用 Json 工具类进行 json 转换
Json.getJson().toJson(...)
Json.getJson().parse(...);
3、使用指定的 json 实现转换
如果下代码将使用指定的 json 实现去转换:
// 临时指定使用 FastJson 实现
FastJson.getJson().toJson(...);
FastJson.getJson().parse(...);
// 为 Controller.renderJson(..) 方法直接传入转换好的 json string
renderJson(FastJson.getJson().toJson(...));
上面这种用法可以临时摆脱配置的 json 实现,从而使用指定的 json 实现。
5、不输出 null 值字段
序列化时默认会把 null 值字段一起写出来(例如 {"ok":true,"msg":null,"error":null})。有三种方式,按影响范围选。
一、按调用点跳过:getSkipNullJson() / JsonUtils.toSkipNullJson()
Json.getSkipNullJson().toJson(object);
JsonUtils.toSkipNullJson(object);
只影响这一次调用。"必须显式传 null"的报文(某些第三方接口)应当继续用 Json.getJson()。
二、全局开关:配置项 tio.json.skipNull
# 打开之后,所有走默认工厂的序列化都不再输出 null 值字段
tio.json.skipNull=true
默认 false,所以不配置的项目行为一点不变。配置项在默认工厂初始化时读取;与其它配置项一样,四条来源(命令行 / 配置文件 / JVM 参数 / 环境变量)都认,优先级由配置读取链统一决定。
三、启动期才决定时用代码打开:Json.installSkipNull()
配置项只在静态初始化时读一次。如果这个开关要到项目自己的启动钩子里才能确定,那时默认工厂可能已经建好了 —— 用这个方法直接切过去,它与配置项等效、且重复调用安全:
// 项目启动钩子里(例如 BootConfiguration 的 config())
boolean skipNull = Json.installSkipNull(); // 传 true 与不传参等价
// 需要回退时:
Json.uninstallSkipNull(); // 只还原"由代码装上的那一层"
installSkipNull(false) 与 uninstallSkipNull() 等价:只还原自己装的那一层,别人装的(或配置本来就打开的)一律不动 —— 否则"关掉"就变成了"拆掉别人的设置"。还原还的是安装之前那个工厂实例,所以 provider、日期格式等之前的选择不会被抹掉。
想知道现在到底是什么状态,用 Json.isSkipNull():它按输出的实际表现判断,对 fastjson / gson / jackson / 混合实现都成立。
影响范围是"这个进程",不是"这个模块"
默认工厂是静态字段,同一个 JVM 里被依赖进来的模块会一起变:响应体、controller 返回值、swagger 文档、session 存值,以及出站请求体(Http、Telegram、企业微信 / 飞书通知等工具都用 JsonUtils.toJson(..) 拼报文)全都会跳过 null。所以:
- 只想改响应体、又要连出站报文一起保住的,不要打开全局开关,改成在响应那个出口用
Json.getSkipNullJson(); - 确定本进程没有依赖"显式 null 的报文"(例如只对外提供 HTTP 接口、不发这类通知),打开全局开关最省事;
- 全局开关是"每个项目一份配置":写在自己项目的配置里,打开只影响自己那一个进程,不会影响同框架下的其它项目(它们各自是独立进程、各自一份静态状态)。
以浏览器服务为例:它在 app.properties 里打开了这个开关(HTTP 响应不再输出每一条都带的 msg / error 空字段),启动时用 Json.installSkipNull() 与项目自己的配置项对齐,并把结果打进启动日志 —— 配置有没有生效一眼可见。
