本文是 JsonUtil 类 cheat sheet 的姊妹篇;
JsonUtil 类 cheat sheet 侧重于代码层面对 jackson API 的使用, 是一个 “宏观” 的行为: 其设置对全局皆有效; 而本文则着重讨论 javabean 各成员上 jackson 注解的使用, 是一个 “微观” 的行为: 通过注解的约定, 可以精细化控制具体的类, 字段及方法 的序列化 / 反序列化行为, 使 jackson 能够定制化得处理各个 javabean;
字段是否参与序列化
控制字段是否参与序列化是精细化管理 jackson 行为的典型案例:
1 | public class Bean { |
反序列化相关注解
jackson 在序列化时使用反射, 故而其对目标类的构造器情况完全没有要求; 然而在反序列化时, jackson 默认使用无参构造器创建实例, 如果对象没有无参构造器, 也没有任何注解提示 jackson, 则会反序列化失败:
1 | Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `xxx` |
如果想提示 jackson 使用有参构造器, 需要注解如下:
1 |
|
不过, 在大部分场景下, 光靠一个 @JsonCreator
注解是不够的, 只要构造器含有多于一个的参数, jackson 便会报如下异常:
1 | Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Argument #0 of constructor [constructor for xxx, annotations: |
此时需要另一个注解 @JsonProperty
放置在构造器的每一个参数上:
1 |
|
其实, @JsonProperty
的使用是独立于 @JsonCreator
的, 其还可以作用于成员字段或成员方法上, 不过作用却是一致的, 均是用于反序列化时作为 json 字段与类成员字段的映射;
当然其中也有一个细微差别, 作用于字段或方法上的 @JsonProperty
的 value 是可以为空的: @JsonProperty
的 value 如果为空, 则 jackson 会寻找 json 字符串中与该字段名一致的 key; 如果 value 非空, 则 jackson 会寻找 json 字符串中与该 value 一致的 key;
而对于构造器方法, @JsonProperty
的 value 是不可以为空的, 毕竟构造器方法的参数名是可以与成员字段名不一样的;
多态反序列化
有些特殊的场景下, 一串 json 可能对应于继承一个父类的多个子类, 然而究竟对应于哪个子类, 却无法在代码中提前确定下来, 只能以父类型作为 valueType
参与反序列化, 这种时候就需要使用 jackson 的多态反序列化功能:
1 | "@class") (use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = |
对于以上配置, 以子类 Child1 为例, 相应的 json 字符串内容如下:
1 | jsonStr = { |
要让 jackson 识别传入的 json 串究竟表示哪一个子类, json 中就必须要存在父类中定义 @JsonTypeInfo
时约定的 property name, 以上例子中的 property 是 “@class”; 另外, property “@class” 对应的值也必须是父类中定义 @JsonSubTypes
时约定的几个 name, jackson 以此确定究竟反序列化为哪个子类, 以上例子中, “Child1” 对应于 Child1.class, “Child2” 对应于 Child2.class;
现在, 使用以下代码便可以让 jackson 将刚才定义的 jsonStr 串反序列化为一个 Child1 实例, 而代码中我们仅仅需要指定 valueType
为 Parent.class:
1 | objectMapper.readValue(jsonStr, Parent.class); |
循环依赖的解除 (字段的排除)
有时候会遇到蛋疼的问题: 两个类互相引用, 这时 jackson 会判定其发生循环依赖, 无法序列化; 对这种情况我们就需要手动解开循环依赖, 比较典型的方法是忽略涉及循环依赖的字段, 不过这样可能造成信息丢失:
1 | public class Bean { |