在开发业务中有一个请求接受实体大致如下:
@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;
}
}