记录Jackson和Lombok的坑
记录Jackson和Lombok的坑
今天遇到Jackson反序列化json缺少了字段,后来研究下发现是Jackson的机制和Lombok生成的setter不一致,导致没有正确调用setter。
复现
Java实体类
@Data
public class DemoData{
private Double t;
private Double eDay;
}
Json字符串
{
"t":12.23,
"eDay":123.321
}
使用Jackson解析下来,发现只有t有值,而eDay没有解析到。
原因分析
首先第一反应是Lombok生成的getter和setter也许有问题,于是去掉@Data
注解,用IDEA生成getter和setter,再进行反序列化,发现已经可以正常反序列化了。
于是看了下编译生成的代码:
public class DemoData{
private Double t;
private Double eDay;
public Double getT() {
return this.t;
}
public Double getEDay() {
return this.eDay;
}
public void setT(final Double t) {
this.t = t;
}
public void setEDay(final Double eDay) {
this.eDay = eDay;
}
}
去掉lombok的注解,直接用IDEA生成getter和setter,生成之后是这样的:
public class DemoData{
private Double t;
private Double eDay;
public Double getT() {
return t;
}
public void setT(Double t) {
this.t = t;
}
public Double geteDay() {
return eDay;
}
public void seteDay(Double eDay) {
this.eDay = eDay;
}
}
显然两边的Getter和Setter是不一样的,那么Jackson是怎么寻找属性和Setter的呢?
Jackson2在初始化序列器时,对pojo类型对象会收集其属性信息,属性包括成员变量及方法,然后属性名称和处理过后的方法名称做为key保存到一个LinkedHashMap中。
收集过程中会调用com.fasterxml.jackson.databind.util.BeanUtil中的legacyManglePropertyName方法用来处理方法名称,它会将get/set方法前缀,即get或set去掉,并将其后面的连续大写字符转换成小写字符返回。
例如: getNEWString会转变成newstring返回。你的属性名称如果有这样的"nSmallSellCount",lombok自动生成的get方法就会是这样的"getNSmallSellCount",处理过后就是这样的"nsmallSellCount",这与属性nSmallSellCount并不冲突,可以同时存在于HashMap中。
所以,当Jackson扫描由Lombok生成的POJO时,读取到setEDay,会把set去掉,拿到EDay,然后转成eday。由此导致json中的eDay属性在LinkedHashMap中没有找到setter方法,反序列化就丢失了字段。
所以原因已经确定了:当使用Lombok修饰的POJO中存在由aAxxx这样的(单个小写字母跟着大写字母)的属性时,反序列化会丢失这个字段。
如何解决
DeLombok
当代码中出现这样的字段时,由IDEA生成对应的getter和setter,会自动覆盖lombok生成的方法。
使用Builder来做Jackson的反序列化器
Lombok似乎意识到了这个问题(所以为啥不改下setter的生成呢???),编写了@Jacksonized
这个注解来为Jackson反序列提供支持,但是这个注解必须配合@Builder
或者@SuperBuilder
一起使用才会生效。
我们看下@Jacksonized
的官方说明:
/**
* The {@code @Jacksonized} annotation is an add-on annotation for
* {@code @}{@link Builder} and {@code @}{@link SuperBuilder}. It automatically
* configures the generated builder class to be used by Jackson's
* deserialization. It only has an effect if present at a context where there is
* also a {@code @Builder} or a {@code @SuperBuilder}; a warning is emitted
* otherwise.
* <p>
* In particular, the annotation does the following:
* <ul>
* <li>Configure Jackson to use the builder for deserialization using
* {@code @JsonDeserialize(builder=Foobar.FoobarBuilder[Impl].class)}
* on the class (where <em>Foobar</em> is the name of the annotated class).</li>
* <li>Copy Jackson-related configuration annotations (like
* {@code @JsonIgnoreProperties}) from the class to the builder class. This is
* necessary so that Jackson recognizes them when using the builder.</li>
* <li>Insert {@code @JsonPOJOBuilder(withPrefix="")} on the generated builder
* class to override Jackson's default prefix "with". If you configured a
* different prefix in lombok using {@code setterPrefix}, this value is used. If
* you changed the name of the {@code build()} method using using
* {@code buildMethodName}, this is also made known to Jackson.</li>
* <li>For {@code @SuperBuilder}, make the builder implementation class
* package-private.</li>
* </ul>
* This annotation does <em>not</em> change the behavior of the generated builder.
* A {@code @Jacksonized} {@code @SuperBuilder} remains fully compatible to
* regular {@code @SuperBuilder}s.
*/
简单来说,这个注解会做下面的事:
- 会通过
@JsonDeserialize
注解让Jackson使用Builder来构建对象; - 拷贝Jackson相关的注解到Builder中(比如
@JsonIgnoreProperties
); - 生成的Builder类会添加
@JsonPOJOBuilder
注解并写入prefix;
因此,把上面的Pojo改写成这样:
@Data
@Builder
@Jacksonized
public class DemoData {
private Double t;
private Double eDay;
}
会生成下面的POJO:
@JsonDeserialize(
builder = DemoData.DemoDataBuilder.class
)
public class DemoData {
private Double t;
private Double eDay;
DemoData(final Double t, final Double eDay) {
this.t = t;
this.eDay = eDay;
}
public static DemoData.DemoDataBuilder builder() {
return new DemoData.DemoDataBuilder();
}
public Double getT() {
return this.t;
}
public Double getEDay() {
return this.eDay;
}
public void setT(final Double t) {
this.t = t;
}
public void setEDay(final Double eDay) {
this.eDay = eDay;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof DemoData)) {
return false;
} else {
DemoData other = (DemoData)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$t = this.getT();
Object other$t = other.getT();
if (this$t == null) {
if (other$t != null) {
return false;
}
} else if (!this$t.equals(other$t)) {
return false;
}
Object this$eDay = this.getEDay();
Object other$eDay = other.getEDay();
if (this$eDay == null) {
if (other$eDay != null) {
return false;
}
} else if (!this$eDay.equals(other$eDay)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof DemoData;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $t = this.getT();
int result = result * 59 + ($t == null ? 43 : $t.hashCode());
Object $eDay = this.getEDay();
result = result * 59 + ($eDay == null ? 43 : $eDay.hashCode());
return result;
}
public String toString() {
Double var10000 = this.getT();
return "DemoData(t=" + var10000 + ", eDay=" + this.getEDay() + ")";
}
@JsonPOJOBuilder(
withPrefix = "",
buildMethodName = "build"
)
public static class DemoDataBuilder {
private Double t;
private Double eDay;
DemoDataBuilder() {
}
public DemoData.DemoDataBuilder t(final Double t) {
this.t = t;
return this;
}
public DemoData.DemoDataBuilder eDay(final Double eDay) {
this.eDay = eDay;
return this;
}
public DemoData build() {
return new DemoData(this.t, this.eDay);
}
public String toString() {
return "DemoData.DemoDataBuilder(t=" + this.t + ", eDay=" + this.eDay + ")";
}
}
}
此时,Jackson会使用建造者方法来构建对象,写入属性,Json也可以正常解析了。
记录Jackson和Lombok的坑的更多相关文章
- CozyRSS开发记录0-RSS阅读器开坑
CozyRSS开发记录0-RSS阅读器开坑 1.RSS RSS,全名是Really Simple Syndication,简易信息聚合. 关于RSS相关的介绍,网上可以很容易的找到.RSS阅读器是我几 ...
- lombok踩坑与思考
虽然接触到lombok已经有很长时间,但是大量使用lombok以减少代码编写还是在新团队编写新代码维护老代码中遇到的. 我个人并不主张使用lombok,其带来的代价足以抵消其便利,但是由于团队编码风格 ...
- Jackson中的那些坑
不符合驼峰规范的变量 “驼峰命名法”请自行百度.简单的来说就是变量的第一个单词以小写字母开始其他单词首字母大写,或者全部单词首字母都大写,分别称为“小驼峰”和“大驼峰” 比如一个符合驼峰规范命名的实体 ...
- 【bug记录】OS Lab4 踩坑记
OS Lab4 踩坑记 Lab4在之前Lab3的基础上,增加了系统调用,难度增加了很多.而且加上注释不详细,开玩笑的指导书,自己做起来困难较大.也遇到了大大小小的bug,调试了一整天. 本文记录笔者在 ...
- 【bug记录】OS Lab3 踩坑记
OS Lab3 踩坑记 Lab3在之前Lab2的基础上,增加了进程建立.调度和中断异常处理.其中测试包括进程建立以及进程调度部分. 由于是第一次做bug记录,而且是调试完bug后再做的记录,所以导致记 ...
- 记录使用Buildbot遇到的坑
Buildbot Tips Buildbot也是个大坑..我并不熟悉python,偏偏文档又少.这几天使用buildbot出了不少坑.有的解决了,有的绕过去,这里都把它们一一记下来. Force Bu ...
- 【Java】Jackson解析xml的坑
为了获取xml数据,在spring mvc中针对 @ResponseBody配置了jackson. 刚用的时候内心是狂喜的,终于不用自己解析了---- but----------还是有坑的-- 坑一 ...
- ios 记录支付宝集成遇到的坑及解决方法
今天项目中要开始动手集成支付宝支付,在此小结一下.(目前新版的支付宝SDK有较大改版,去集成还需要自己去开发平台详细的按照集成步骤来完成https://doc.open.alipay.com/docs ...
- 记录pageHelper分页orderby的坑
pageHelper的count查询会过滤查询sql中的order by条件! pageHelper分页功能很强大,如果开启count统计方法,在你执行查询条件时会再执行一条selet count(* ...
随机推荐
- 语义分割:基于openCV和深度学习(一)
语义分割:基于openCV和深度学习(一) Semantic segmentation with OpenCV and deep learning 介绍如何使用OpenCV.深度学习和ENet架构执行 ...
- 视频处理器为电池供电的设计提供4K视频编码
视频处理器为电池供电的设计提供4K视频编码 Video processor enables 4K video coding for battery-powered designs OmniVision ...
- 操作系统-Linux命令
一.目录结构 #因为根目录与开机有关,开机过程中仅有根目录会被挂载, 因此根目录下与开机过程有关的目录(以下5个),不能与根目录放到不同的分区去. /etc:配置文件 /dev:所需要的装置文件 /l ...
- mybatis学习——映射器(mappers)
在定义 SQL 映射语句之前,我们需要告诉 MyBatis 到哪里去找到这些语句. 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射 ...
- IDA反汇编EXE添加一个启动时的消息框
IDA反汇编EXE添加一个启动时的消息框 上一篇文章介绍了用OD反汇编EXE添加一个启动时的消息框,这篇文章也是实现同样的效果,这边主要的思路还是将其反汇编得到汇编代码后,然后手动修改他的逻辑首先跳转 ...
- 学废了系列 - WebGIS vs WebGL图形编程
目前工作中有不少涉及到地图的项目,我参加了几次技术评审,前端伙伴们在 WebGIS 方面的知识储备稍有不足,这次分享的主要目的是科普一些在前端领域比较常用的 WebGIS 知识.另外,我之前的工作中积 ...
- StackOverflow上面 7个最好的Java答案
StackOverflow发展到目前,已经成为了全球开发者的金矿.它能够帮助我们找到在各个领域遇到的问题的最有用的解决方案,同时我们也会从中学习到很多新的东西.这篇文章是在我们审阅了StackOver ...
- Python分析44130条用户观影数据,挖掘用户与电影之间的隐藏信息!
01.前言 很多电影也上映,看电影前很多人都喜欢去 『豆瓣』 看影评,所以我爬取44130条 『豆瓣』 的用户观影数据,分析用户之间的关系,电影之间的联系,以及用户和电影之间的隐藏关系. 02.爬取观 ...
- git分支案例
- 暑假自学java第一天
今天通过网上的学习资料安装了Java的环境和java的程序开发工具包(JDK) 还安装了eclipse ,英语不太好,所以不太会用这个软件,网上搜了教程,还是出现了问题:unnamed package ...