今天遇到Jackson反序列化json缺乏了字段,后来研究下发现是Jackson的机制和Lombok生成的setter不一致,致使没有正确调用setter。java
Java实体类json
@Data public class DemoData{ private Double t; private Double eDay; }
Json字符串ide
{ "t":12.23, "eDay":123.321 }
使用Jackson解析下来,发现只有t有值,而eDay没有解析到。ui
首先第一反应是Lombok生成的getter和setter也许有问题,因而去掉@Data
注解,用IDEA生成getter和setter,再进行反序列化,发现已经能够正常反序列化了。this
因而看了下编译生成的代码:code
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,生成以后是这样的:xml
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中。rem
因此,当Jackson扫描由Lombok生成的POJO时,读取到setEDay,会把set去掉,拿到EDay,而后转成eday。由此致使json中的eDay属性在LinkedHashMap中没有找到setter方法,反序列化就丢失了字段。字符串
因此缘由已经肯定了:当使用Lombok修饰的POJO中存在由aAxxx这样的(单个小写字母跟着大写字母)的属性时,反序列化会丢失这个字段。
当代码中出现这样的字段时,由IDEA生成对应的getter和setter,会自动覆盖lombok生成的方法。
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来构建对象;@JsonIgnoreProperties
);@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也能够正常解析了。