使用 @JsonProperty 注解标注属性导致序列化时多出一个字段的原因
上篇文章 @RequestBody 注解无法解析对象中首字母小写,第二个字母大写的属性(Jackson 的原因) 中说到了使用 @JsonProperty 标注属性时,序列化会多出一个字段,现在我们来探索一下原因
2024-4-17
思路参考 https://blog.csdn.net/shuair/article/details/121234146
在我的多次测试之下发现,Jackson 的默认序列化会调用该类的所有 get 方法,从而多出标注了 get 的字段
而 @JsonProperty 标注到首字母大写的属性上时,从序列化结果上来说会使属性多出一个首字母到第二个小写字母之间的字母都小写的字段,相当于直接多了个对应的 get 方法
对 Jackson 注解的源码进行追溯(可以跳过)
会先到 com.fasterxml.jackson.databind.introspect 包下的 JacksonAnnotationIntrospector 类
- 在 com.fasterxml.jackson.databind.ser.BeanSerializerFactory 的 createSerializer 方法中创建序列化程序
- 在 JacksonAnnotationIntrospector 类中
- 对于在类中找到的 get 方法,执行 findNameForSerialization 方法查找要序列化的名称,在这个方法中会扫描在序列化的类中标注了
@JsonGetter
和@JsonProperty
的属性和方法 - 对于在类中找到的 set 方法,执行 findNameForDeserialization 方法查找反序列化的名称,在这个方法中会扫描在序列化的类中标注了
@JsonSetter
和@JsonProperty
注解的属性和方法
- 对于在类中找到的 get 方法,执行 findNameForSerialization 方法查找要序列化的名称,在这个方法中会扫描在序列化的类中标注了
- 之后到 DefaultAccessorNamingStrategy 类的 legacyManglePropertyName 方法,根据 get/set 方法的方法名拿到字段属性
所以,属性上标注了 @JsonProperty 之后会在序列化时多出一个字段,因为属性被 @JsonProperty 重新定义了拿到的字段名,而代码中的get方法还在,于是在扫描到 get 方法的时候,会发现没有重复的字段(因为被 @JsonProperty 重命名了),于是直接变成序列化的新字段,但值和标志了 @JsonProperty 的属性是一样的(毕竟 get 方法返回的就是这个属性的值)
官方文档的说明与测试结果(只看这个)
https://github.com/FasterXML/jackson-databind?tab=readme-ov-file#annotations-changing-property-names
官方文档的实例
public class MyBean {
private String _name;
// without annotation, we'd get "theName", but we want "name":
@JsonProperty("name")
public String getTheName() { return _name; }
// note: it is enough to add annotation on just getter OR setter;
// so we can omit it here
public void setTheName(String n) { _name = n; }
}
@JsonProperty、@JsonGetter、@JsonSetter 源码中的第一行文档注释:
注:详细注释请看源码
@JsonProperty
Marker annotation that can be used to define a non-static method as a "setter" or "getter" for a logical property (depending on its signature), or non-static object field to be used (serialized, deserialized) as a logical property.
—————————————
翻译:标记注解,可用于将非静态方法定义为逻辑属性(取决于其签名)的“setter”或“getter”,或将要用作(序列化、反序列化)逻辑属性的非静态对象字段。
@JsonGetter
Marker annotation that can be used to define a non-static, no-argument value-returning (non-void) method to be used as a "getter" for a logical property. It can be used as an alternative to more general JsonProperty annotation (which is the recommended choice in general case).
—————————————
标记注解,可用于定义一个非静态、无参数值返回(非void)方法,用作逻辑属性的“getter”。它可以作为更通用的JsonProperty注释(在一般情况下是推荐的选择)的替代方案。
@JsonSetter
Annotation that can be used to define a non-static, single-argument method to be used as a "setter" for a logical property as an alternative to recommended JsonProperty annotation; or (as of 2.9 and later), specify additional aspects of the assigning property a value during serialization.
—————————————
可用于定义非静态单参数方法的注释,该方法将用作逻辑属性的“setter”,作为推荐JsonProperty注释的替代方法;或者(从2.9及更高版本开始),指定在序列化期间为属性赋值的其他方面。
大概的意思是,只需要将 @JsonProperty 注解放到 get 方法上就行
在实际测试中发现,只需要满足以下两个条件之一,该字段无论是序列化还是反序列化都不会出现问题(至少在简单类上是这样)
- 在 get 方法上标注 @JsonGetter(“name”) 或者 @JsonProperty(“name”) ————推荐在 get 方法上标注 @JsonProperty
- 在 set 方法上标注 @JsonSetter(“name”) 或者 @JsonProperty(“name”)
测试代码:
static class C1 {
// @JsonProperty("cId") // 序列化时会多一个字段
// @JsonGetter("cId") // 无法在属性上标注
// @JsonSetter("cId") // 反序列化中有效,序列化不生效
private String cId;
public C1() {
}
public C1(String cId) {
this.cId = cId;
}
@JsonProperty("cId")
// @JsonGetter("cId")
public String getCId() {
return cId;
}
// @JsonProperty("cId")
// @JsonSetter("cId")
public void setCId(String cId) {
this.cId = cId;
}
public String toString() {
return "C1{cId = " + cId + "}";
}
}
@Test
public void testC1() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
C1 c1 = new C1("12345");
String s = objectMapper.writeValueAsString(c1);
System.out.println("对象转json = " + s);
String json = "{\"cId\":\"12\"}";
C1 readValue = objectMapper.readValue(json, C1.class);
System.out.println("json转对象 = " + readValue);
}