纪录Jackson和Lombok的坑

今日碰到Jackson反序列化json缺乏了字段名,之后科学研究下发觉是Jackson的体制和Lombok转化成的setter不一致,造成沒有恰当启用setter。

重现

Javadao层

@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.
 */

简易而言,这一注释会做下边的事:

  1. 会根据@JsonDeserialize注释让Jackson应用Builder来搭建目标;
  2. 复制Jackson有关的注释到Builder中(例如@JsonIgnoreProperties);
  3. 转化成的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还可以一切正常分析了。

评论(0条)

刀客源码 游客评论