当前位置:网站首页>Jackson 使用样例
Jackson 使用样例
2022-08-04 05:33:00 【FA-117】
目录
前言
此处总结下个人使用Jackson的场景,希望对你有所帮助。
一、反序列化日期参数
在spring mvc环境下客户端给的是"yyyy-MM-dd HH:mm:ss",字符串类型;后端参数中具体字段参数是java.util.Date类型,直接接收时会报错。一种方案是,基于mvc框架自定义类型反序列化,另一种方案是在字段上增加Jackson Annotation。
@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
二、反序列化本地多态参数
假设我们需要设计一个通用接口。因为“通用”,所以部分字段肯定是个抽象类型。这样就把通用的框架和具体的业务逻辑拆分开了。
1. 通用Request和通用的RequestParam
@Slf4j
public class Request{
// 请求的路径
private String path;
// 请求的参数
private RequestParam param;
// 请求的方法
private String method;
}
public interface RequestParam {
}
2. 按ID请求参数
@Slf4j
public class ByIdParam implements RequestParam{
// 开始ID
private Long start;
// 结束ID
private Long end;
// 每页数量
private int pageSize;
}
3. 按时间范围请求
@Slf4j
public class ByTimeParam implements RequestParam{
// 开始日期
@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date startTime;
// 结束日期
@JsonFormat(pattern ="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date endTime;
// 每页数量
private int pageSize;
}
4.具体使用
public QueryService {
public Response query(Request request) {
return xxx;
}
}
到这里背景已交代清楚了,接下来咱们把注意力放在注解上。使用Jackson直接通过String反序列化会报错,因为param字段的具体类型无法获取。问题代码如下
ObjectMapper mapper = new ObjectMapper();
String paramStr = "";
mapper.readValueAsObject(paramStr, Request.class);
接下来上注解解决该问题:
5. 添加注解
解决思路就是,告诉Jackson具体的类型是什么。告诉的具体实现方式有两类,1类是在参数中增加“@class”字段指向目标类,这样原有的代码都不用调整。另一类则是在各个子类中增加类似type这样的字段作为类型标识,传递的参数中必须传递该标识。
先看第一种解决思路
@Slf4j
public class Request{
// 请求的路径
private String path;
// 请求的参数
@JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS)
private RequestParam param;
// 请求的方法
private String method;
}
要求在JSON参数中增加"@class":"${具体的class全路径名称}",样例如下
{
"path": "/query",
"method": "GET",
"param": {
"startId": 2,
"endId": 20,
"pageSize": 500,
"@class": "ByIdParam" // 此处为新增加内容以方便Jackson做反序列化
}
}
第二种方法,增加标识属性,且增加子类类型和标识属性值说明
@Slf4j
public class Request{
// 请求的路径
private String path;
// 请求的参数
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type" ,visible = true)
@JsonSubTypes({
@JsonSubTypes.Type(value = ByTimeParam.class, name = "2"),
@JsonSubTypes.Type(value = ByIdParam.class, name = "1")
})
private RequestParam param;
// 请求的方法
private String method;
}
{
"path": "/query",
"method": "GET",
"param": {
"startId": 2,
"endId": 20,
"pageSize": 500,
"@type": 1 // 此处为新增加内容以方便Jackson做反序列化
}
}
站在前后端交互角度,后者更合适一些。因为参数可以不依赖具体的类名称,进而不影响后端代码的包层次结构调整。
三、反序列化第三方包中的多态参数
前面讲到的场景中,参数类是自己定义和实现的。如果pojo是第三方提供的jar包,这种方案是不行的。比如,笔者在工作中需要依赖地理对象表示的库GeoJson,其中对空间对象,比如点,线,面是通过不同的子类来表示的。显然这也是一个多态参数接收的场景。但是奈何jar包是第三方的,此时就需要自定义反序列化类。样例代码如下:
@Slf4j
public class GeoJsonDeserializer extends JsonDeserializer<GeoJson> {
private static final String TYPE_FIELD = "type";
private static final Map<String, Class<? extends GeoJson>> TYPE_MAP = new HashMap<>();
static {
// place holder object
GeoJson lineString = null;
Point point = new Point(0d, 0d);
List<Point> points = Arrays.asList(point);
lineString = new GeoJsonPoint(point);
TYPE_MAP.put(lineString.getType(), lineString.getClass());
lineString = new GeoJsonMultiPoint(points);
TYPE_MAP.put(lineString.getType(), lineString.getClass());
lineString = new GeoJsonLineString(points);
TYPE_MAP.put(lineString.getType(), lineString.getClass());
lineString = new GeoJsonMultiLineString(points);
TYPE_MAP.put(lineString.getType(), lineString.getClass());
lineString = new GeoJsonPolygon(points);
TYPE_MAP.put(lineString.getType(), lineString.getClass());
lineString = new GeoJsonMultiPolygon(Arrays.asList((GeoJsonPolygon)lineString));
TYPE_MAP.put(lineString.getType(), lineString.getClass());
// for gc
lineString = null;
points = null;
}
public boolean support(String type) {
return type != null && TYPE_MAP.containsKey(type);
}
public String getType(JsonNode node) {
JsonNode typeNode = node.get(TYPE_FIELD);
if(typeNode == null) {
log.warn("field {} is absent", new Object[]{TYPE_FIELD});
return null;
}
String type = typeNode.asText();
log.info("get to deserialize geo type: " + type);
return type;
}
@Override
public GeoJson deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = p.getCodec().readTree(p);
String geoType = this.getType(node);
if(!this.support(geoType)) {
log.warn("unsupported geo type " + geoType);
return null;
}
ObjectMapper objectMapper = new ObjectMapper();
Class<? extends GeoJson> geoJsonClass = TYPE_MAP.get(geoType);
JsonMapper mapper = new JsonMapper();
GeoJson result = objectMapper.readValue(mapper.createParser(node.toString()), geoJsonClass);
return result;
}
}
代码中的核心思路是:
1. 找到具体的子类,样例中通过type字段;
2. 通过ObjectMapper和子类class完成反序列化;
当然,单纯有这个Deserialiazer没用,还需要配置到POJO中。
@Slf4j
public class Request{
// 请求的路径
private String path;
// 请求的参数
@JsonDeserialize(as=GeoJsonDeserializer.class),
private GeoJson param;
// 请求的方法
private String method;
}
四. 序列化相关设置
@JsonIgnore,序列化时忽略该属性,当一个Java对象中仅部分字段需要序列化时使用。
@JsonProperty("firstName"),允许JSON字段名和本地Java属性名不一致。通常在维护旧系统时,增加feature同时又必须兼容现有功能时使用。
五、前后端交互中的ID处理
Java中Long或者BigInteger能够表示的范围非常大,但是JS中Number能够表示的最大范围有限,笔者在业务系统开发时,就遇到这么个坑。当数值超过一定范围,前端展示的所有ID都一样。当存在数据更新时,那画面简直不敢想象。因为JS能够标识的最大整数有限。
Number.MAX_SAFE_INTEGER 的结果是 900719 9254 740991。
笔者在开发一个日志文件处理系统时,希望给每一行日志设置一个ID,大概是yyyyHHMM {4位文件号} {6位的文件行号} 直接就超标了。因此在序列化时,一种方案自定义序列化超过该数字时结尾加n, 让浏览器识别为BigInt,另一种则是转换为string,毕竟string可以标识的数据可以非常长。
总结
以上就是个人对Jackson的使用小结,希望对你有所帮助。如你有更好的案例,也欢迎分享探讨。
边栏推荐
猜你喜欢
随机推荐
文件编辑器
学好网络安全看这篇文章让你少走弯路
file editor
【HIT-SC-MEMO2】哈工大2022软件构造 复习笔记2
位段-C语言
安装Apache服务时出现的几个问题, AH00369,AH00526,AH00072....
基于Webrtc和Janus的多人视频会议系统开发4 - 改造信令交互系统完成sdp交换过程
沉浸式体验参加网络安全培训班,学习过程详细到底!
基于语音识别的QT设计的csgo互动类视频游戏
以太网 ARP
JVM三大常量池与方法区
MVC custom configuration
MySQL stored procedure study notes (based on 8.0)
An abstract class, internal classes and interfaces
C语言结构体(必须掌握版)
【HIT-SC-LAB2】哈工大2022软件构造 实验2
Stream API
Miscellaneous [development] [VS Code] remote - SSD retry failed
JUC并发容器——跳表
SSO单点登陆