天天看点

传参请求时jackson大小写取值异常和序列化多出一个属性的问题学习记录

在开发业务中有一个请求接受实体大致如下:

@Data
public class ActivityAddQuery {
    /**
     * a类标签
     */
    @NotBlank(message="a类标签不能为空")
    @Length(max=64,message = "")
    private String aLabel;
    }
           

在前端请求参数设置为{“aLabel”:“aaa”}时后端接受到的参数一直为空,查询资料发现,spring的jackson参数发现顺序,依次如下:

1.所有被public修饰的字段;

2.所有被public修饰的getter;

3.所有被public修饰的setter。

由于aLabel被设置成private,不符合第1条,到了第二条得到的是getALabel()方法,jackson不知道第一个A是大写还是小写,默认将从第一个大写开始的所有大写字母识别成小写,即alabel,读取setALabel()也一样。例如属性aLLbel也会由getALLabel()识别成allbel。

解决方案: 添加@JsonProperty(“aLabel”)指定json名称即可。

@Data
public class ActivityAddQuery {
    /**
     * a类标签
     */
    @JsonProperty("aLabel")
    private String aLabel;
    }
           

想要知道原因,首先查看编译之后的的class为

@Data
public class ActivityAddQuery {
    /**
     * a类标签
     */
    @JsonProperty("aLabel")
    private String aLabel;
   
	 public String getALabel() {
            return this.aLabel;
     }

     @JsonProperty("aLabel")
      public void setALabel(String aLabel) {
          this.aLabel = aLabel;
      }
 }
           

稍微debug一下jackson,截取一部分如下:

protected void collectAll()
    {
        LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();

        // First: gather basic data
        _addFields(props);
        _addMethods(props);
        // 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static
        //    inner classes, see [databind#1502]
        if (!_classDef.isNonStaticInnerClass()) {
            _addCreators(props);
        }
        _addInjectables(props);

        // Remove ignored properties, first; this MUST precede annotation merging
        // since logic relies on knowing exactly which accessor has which annotation
        _removeUnwantedProperties(props);
        // and then remove unneeded accessors (wrt read-only, read-write)
        _removeUnwantedAccessor(props);

        // Rename remaining properties
        _renameProperties(props);

        // then merge annotations, to simplify further processing
        // 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of
        //   annotations from getting properly merged
        for (POJOPropertyBuilder property : props.values()) {
            property.mergeAnnotations(_forSerialization);
        }

        // And use custom naming strategy, if applicable...
        PropertyNamingStrategy naming = _findNamingStrategy();
        if (naming != null) {
            _renameUsing(props, naming);
        }

        /* Sort by visibility (explicit over implicit); drop all but first
         * of member type (getter, setter etc) if there is visibility
         * difference
         */
        for (POJOPropertyBuilder property : props.values()) {
            property.trimByVisibility();
        }

        /* and, if required, apply wrapper name: note, MUST be done after
         * annotations are merged.
         */
        if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {
            _renameWithWrappers(props);
        }

        // well, almost last: there's still ordering...
        _sortProperties(props);
        _properties = props;
        _collected = true;
    }
           

序列化的步骤大致如下:

  • 首先是读取属性----- _addFields(props);

    将所以可能需要序列化的属性加入到未过滤的属性集中,即将属性aLabel加入到 props 中。(在之后的过滤中,一般会过滤掉非public修饰的属性,但如果有@JsonProperty注解,一样会保留)。

  • 然后再读取get、set和@JsonAnySetter注解的方法------ _addMethods(props);

    将所以可能需要序列化的由get、set和@JsonAnySetter注解的方法中获取的属性加入到未过滤的属性集中。由于读取规则,将getALbel()和setALbel()读取成alabel,于是将属性alabel加入到 props 中。

  • 之后就是一系列的过滤和名称修正,此时props中其实有alabel和aLabel两个属性,但是在之后的属性过滤中,由getALbel和setALbel读取来的alabel属性,在getALabel上有 @JsonProperty(“aLabel”)明确的命名(nameExplicit)为aLabel,在最后才会得到正确的唯一的json属性aLabel。

有时候序列化之后多出来一个属性,这里也就可以解释的通了。例如下:

@Data
public class ActivityAddQuery {
    /**
     * 或者去掉 @JsonProperty("aLabel")注解,将private 改为 public
     */
    @JsonProperty("aLabel")
    private String aLabel;
    }
    public String getALabel() {
            return this.aLabel;
     }

     
      public void setALabel(String aLabel) {
          this.aLabel = aLabel;
      }
           

由于读取类属性是获取到了aLabel,读取get和set方法时读取到了alabel,由于没有上面的对从get和set方法获取来的属性alabel强制命名为aLabel,所以序列化出来是这样的:

所以需要像lombok在get或set上加@JsonProperty强制命名。

其实在idea中右键生成get set方法时,idea会智能的将类似aLabel的可能有歧义的getset方法设置成getaLabel()和setaLabel(),并不会将首字母A大写,也就不会有序列化时的读取歧义。

public class Test {
    
    private String aLabel;
    private String abc;

    public String getaLabel() {
        return aLabel;
    }

    public void setaLabel(String aLabel) {
        this.aLabel = aLabel;
    }

    public String getAbc() {
        return abc;
    }

    public void setAbc(String abc) {
        this.abc = abc;
    }
}