今天遇到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也能夠正常解析了。