Jackson 使用样例

目录

前言

一、反序列化日期参数

二、反序列化本地多态参数

1. 通用Request和通用的RequestParam

2. 按ID请求参数

3. 按时间范围请求

4.具体使用

三、反序列化第三方包中的多态参数

四. 序列化相关设置

五、前后端交互中的ID处理

总结


前言

此处总结下个人使用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 {
    private static final String TYPE_FIELD = "type";
    private static final Map> TYPE_MAP = new HashMap<>();

    static {
        // place holder object
        GeoJson lineString = null;

        Point point = new Point(0d, 0d);
        List 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 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的使用小结,希望对你有所帮助。如你有更好的案例,也欢迎分享探讨。

你可能感兴趣的:(Jackson,json,java)