jackson 有各种各样的配置选项 (Feature), 涵盖了包括 json 语法解析, 语句生成, 序列化 / 反序列化特征, 字段类型处理 等不同层面, 对初学者而言, 很容易造成困惑;
本文基于 jackson 2.8.x, 着手整理常用的 jackson 配置选项, 并给出一个便捷的工具类, 以友好的方式整合 jackson 的常用配置项;
配置选项分类
jackson 的配置选项丰富得可以让开发者自定义从 json 语句到 javabean 的方方面面, 总体来说有如下几类:
json 语句的生成与解析
1 | // json 语句生成的选项 |
javabean 的序列化与反序列化
1 | // javabean 序列化选项 |
javabean 字段
1 | // javabean 字段是否参与序列化 |
上述各 Feature 或 Include 都是以枚举 (enum) 的形式定义的, 他们分布在 jackson 的如下包中:
- JsonGenerator.Feature: jackson-core
- JsonParser.Feature: jackson-core
- SerializationFeature: jackson-databind
- DeserializationFeature: jackson-databind
- JsonInclude.Include: jackson-annotations
这样的归类与它们的功能有关:
JsonGenerator 与 JsonParser 主管 json 的生成与解析, 当属 jackson 的核心功能, 所以归于 jackson-core 包中;
SerializationFeature 与 DeserializationFeature 主管 javabean 的序列化与反序列化, 属于数据实体绑定范畴, 所以归于 jackson-databind 包中;
JsonInclude.Include 比较特别, 它理应主管字段是否参与序列化, 是 javabean 序列化的控制细节, 但是它的外部类 JsonInclude 本身是一个注解, 故其被归于 jackson-annotations 包中;
下面将分别总结各类 Feature 中具体的常用选项; 有一点要说的是, 以下选项有些本身就是默认设置, 这里只是拿出来总结一下, 我们需要了解这些设置的存在; 另外, 某些设置可能只针对普遍的情况, 在特殊场景下并不适用 (比如容错性, 在某些严格的环境下就是需要 fast fail, 不需要容错);
JsonGenerator.Feature
其实, JsonGenerator.Feature 是一个比较底层的设置, 在源码注释中被称为 Low-level I/O / content features
;
关于 json 的生成的配置项, 主要是三个方面:
(1) 统一字段的 json 规范形式, 如对引号的要求;
1 | // 必须以 "双引号" 的形式包装字段 |
(2) 加强 json 生成的容错性, 如 JsonToken (大括号与中括号) 的匹配;
1 | // 允许 json 生成器自动补全未匹配的括号 |
(3) 第三个可能比较少见, 因为大部分业务场景下, 我们只是使用 objectMapper.writeValueAsString
方法, 得到其返回的 json 字符串以作他用; 而如果使用 objectMapper.writeValue
方法, 则可能涉及到写入流的问题:
1 | // 允许 json 生成器在写入完成后自动关闭写入的流 |
这个选项默认是开启的, 那么会导致一个问题: 当你希望在往目标输出流里输出 json 之后再输出一些其他内容便会失败, 因为 jackson 已经自动帮你关闭该输出流了; 所以有些时候, 这个选项要设置成 false;
JsonParser.Feature
JsonParser 的配置项, 主要是加强 json 解析的容错性, 并主要体现在三方面:
(1) 降低字段的 json 规范形式, 如对引号的要求; 可以发现, 当自己生成 json 时, 我们要求严格规范引号的书写, 而当解析别人的 json 时, 却需要放宽规范与形式, 增强容错;
1 | // 允许 json 存在没用引号括起来的 field |
(2) 扩大数值字段的表现形式, 如前导 0, 无限大等;
1 | // 允许 json number 类型的数存在前导 0 (like: 0001) |
(3) 允许出现注释;
1 | // 允许 json 存在形如 // 或 /**/ 的注释 |
SerializationFeature
SerializationFeature 的配置项, 主要是针对 javabean 字段序列化作规范与统一;
比如:
(1) 输出压缩的 json, 而不要缩进格式化, 浪费流量;
(2) 统一时间类型的输出为 timestamp, 消除歧义, 方便转换;
(3) 将空的集合类型以空的形式参与序列化, 而不是不展示它们, 从而始终能得到完整的数据结果;
1 | // 序列化时, 禁止自动缩进 (格式化) 输出的 json (压缩输出) |
另外也有一些容错方面的设置:
1 | // 序列化时, 对于没有任何 public methods / properties 的类, 序列化不报错 |
DeserializationFeature
反序列化时配置项, 主要是考虑到容错性, 针对陌生的字段作忽略处理, 从而提高版本之间的兼容性(比如升级某个 api 版本增加字段后, 所有调用方并不需要保持同步升级, 反序列化时对新字段暂时忽略即可);
1 | // 忽略未知的字段 |
JsonInclude.Include
这与 SerializationFeature.WRITE_NULL_MAP_VALUES
有些相似: 就是针对所有的元素, 不管是不是 null, 都要参与序列化, 要展示所有元素的情况, 而不是对空值就忽略不展示了, 那会诱导发生潜在的 bug; 这算是对字段序列化作的规范统一;
1 | // 所有实例中的 空字段, null 字段, 都要参与序列化 |
jackson 选项控制的良好实践
公司的基础公共服务 api 中, 有一组关于 jackson 的良好封装, 旨在以更便捷友好的方式设置 jackson 配置选项, 当时该组件的作者是基础架构部的 杨淼; 不过, 该组件源代码维护在 qunar 的私有 gitlab 仓库中, 并不对外开源; 出于与公司的保密协议, 我只能将我一个使用 scala 开发的个人项目中针对此重新编写的代码拿出来与大家分享: 便捷设置 jackson 配置选项的案例;
这个案例中, 有两点值得分享:
(1) 利用重载方法统一选项设置入口
关于 ObjectMapper 设置选项的 configure 方法, 我们可以发现, 它有很多重载方法:
1 | public ObjectMapper configure(MapperFeature f, boolean state); |
以上五个重载方法已经涵盖了上一节中提到的五种 Feature 中的四种; 最后还有一个关于 JsonInclude.Include 的设置方法如下:
1 | // 针对 JsonInclude.Include |
所以, 在我分享的例子中, 便抽出了一个统一设置各个 Feature 的方法:
1 | private def configure(mapper: ObjectMapper, feature: AnyRef, state: Boolean) { |
(2) 使用位运算符巧妙设置选项
其实, 在 jackson 自己的源码中, 就已经蕴含着位运算的设计思路 (以 SerializationFeature 为例):
1 | /* SerializationFeature */ |
其中, feature.getMask()
方法便是得到目标 feature 的掩码 _mask
, 一个在二进制上与其他 feature 相互错开的整数值:
1 | private SerializationFeature(boolean defaultState) { |
可以发现, 1 << ordinal()
这个操作会给该枚举中的每个值, 依次对 1 作左移, 从而枚举内所有的值, 其二进制表示都只含有一个 1, 且两两错开 (但由于 _mask
是一个普通整型, 所以该枚举只能容纳不超过 32 个值);
那么位或运算 _serFeatures | feature.getMask()
便等于将该目标枚举值追加到掩码中了;
在我分享的例子中, 也是借鉴了这样巧妙的设计思想, 不同之处在于, 我的案例中是将所有常用的选项配置放在了一起, 组成了一个新的枚举, 并添加了一个 enableByDefault, 表示是否默认开启; 在这个新枚举中, 使用位操作对所有常用的配置编码, 统一设置:
1 | val defaults: Long = { |
当不想使用默认设置时, 构建一个新的 ObjectMapper 实例也十分简洁, 只需传入一个掩码映射的数值即可:
1 | private def buildMapperInternal(features: Long): ObjectMapper = { |
以上便是该案例中两个值得学习的设计要点: 基于 jackson 原生代码, 利用方法重载, 位运算符, 整合分散的配置项, 聚集为一个便捷的工具类;