⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7496341504849641510 「洛小豆」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

最近在处理一个 JSON 接口时,遇到这样一种情况:返回的数据中包含一些我事先并不知道的字段,这些字段会根据上下文动态变化,没办法在 Java 类中提前写死字段名。

起初我以为只能通过 Map 手动解析,但后来发现 Jackson 提供了 @JsonAnyGetter 和 @JsonAnySetter 这两个注解,专门用来处理这种“动态属性”。它们能让我优雅地把未知字段收集起来或者序列化出去,不影响已知字段的正常处理。

不过我在使用过程中也有点疑惑,比如这两个注解的用法顺序有什么讲究?有哪些坑需要避免?这种方式是不是适合所有动态字段的场景?

开家杂货铺吧

@JsonAnyGetter/@JsonAnySetter: 像开了一家灵活应对一切需求的「杂货铺」🧃🧂

想象你是个 JSON 杂货铺老板,门口写着招牌:“你有啥,我都能装;你要啥,我都能配。”

你平时会备一些常规商品(字段),但总有顾客带些奇怪需求来问:

  • “老板,有没有草莓味的牙膏?”
  • “能不能加点冰块到辣酱里?”

这些你事先没在货架上准备的“临时需求”,你也得接单,对吧?

这时候你就需要一对“万能架子”——也就是:

@JsonAnySetter:随便放!你给啥我都能接

每次有奇怪字段进来(JSON 反序列化时),你就把它们统统放进一个万能柜子(通常是 Map<String, Object>):

@JsonAnySetter
public void add(String key, Object value) {
otherProps.put(key, value);
}

比如这个 JSON:

{
"name": "豆瓣酱",
"spicy": true,
"limited_edition": "yes",
"extra_notes": "只在冬天卖"
}

你类里只定义了 name 和 spicy 字段,但 limited_edition 和 extra_notes 也能顺利进货,被收纳进了 otherProps 这个万能抽屉里。

@JsonAnySetter 用于标注一个方法,该方法可以接收 JSON 中没有预定义的属性。当 Jackson 反序列化 JSON 时,如果遇到未在 Java 类中显式定义的字段,它会调用这个方法并将字段名和字段值作为参数传递给它。

当你在反序列化 JSON 时,不希望显式定义所有的字段,或者 JSON 中包含了动态的属性时,使用 @JsonAnySetter 可以自动将这些字段添加到一个 Map 或类似的结构中。

接下来用一个完整的代码示例,我们来实现反序列化时动态添加属性:

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

publicclass Person {
private String name;
privateint age;

// 存储额外的动态属性
private Map<String, Object> additionalProperties = new HashMap<>();

// 添加动态属性
@JsonAnySetter
public void addAdditionalProperty(String key, Object value) {
this.additionalProperties.put(key, value);
}

// 省略 getter 和 setter 方法

public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}

public static void main(String[] args) throws Exception {
String json = "{"name":"John","age":30,"address":"123 Street","nickname":"Johnny"}";

ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json, Person.class);

System.out.println("Name: " + person.name); // 输出:Name: John
System.out.println("Age: " + person.age); // 输出:Age: 30
System.out.println("Additional Properties: " + person.getAdditionalProperties());
// 输出:Additional Properties: {address=123 Street, nickname=Johnny}
}
}

在这个例子中:

  • Person 类通过 @JsonAnySetter 注解的 addAdditionalProperty 方法来处理动态的属性。
  • Person 类中没有显式的字段来接收 address 和 nickname,但它们被添加到 additionalProperties 中。
  • 当 Jackson 反序列化 JSON 时,它会把 address 和 nickname 作为动态属性添加到 additionalProperties 中。

输出:

Name: John
Age: 30
Additional Properties: {address=123 Street, nickname=Johnny}

@JsonAnyGetter:随便拿!需要啥我都能给

到了序列化的时候,有顾客问你:“老板,这罐酱料里都包含什么成分?”

你就把主料(已有字段)和万能抽屉里的额外信息一起打包给他,看起来就像全是标准字段一样输出!

@JsonAnyGetter
public Map<String, Object> getOtherProps() {
return otherProps;
}

这样序列化输出的 JSON 会自动把 otherProps 里的内容平铺出去,和其他字段“融为一体”。

@JsonAnyGetter 用于标注一个方法,该方法返回一个 Map 或类似结构,它将包含对象的 动态属性(即对象中没有显式定义的字段)。当 Jackson 序列化对象时,它会将这个 Map 中的键值对当作额外的 JSON 属性来序列化。

当你有一个类,但是它可能会接受动态的字段,或者一些额外的键值对时,使用 @JsonAnyGetter 允许你将这些额外的字段序列化为 JSON。

继续使用上面的person类,它有一些基本的属性,但你希望允许动态添加额外的属性,如额外的 “address“ 、 “nickname“ 等字段。

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

publicclass Person {
private String name;
privateint age;

// 存储额外的动态属性
private Map<String, Object> additionalProperties = new HashMap<>();

public Person(String name, int age) {
this.name = name;
this.age = age;
}

// 通过该方法返回所有额外的动态属性
@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return additionalProperties;
}

public void addAdditionalProperty(String key, Object value) {
this.additionalProperties.put(key, value);
}

// 省略 getter 和 setter 方法
}

publicclass Main {
public static void main(String[] args) throws Exception {
Person person = new Person("John", 30);
person.addAdditionalProperty("address", "123 Street");
person.addAdditionalProperty("nickname", "Johnny");

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(person);
System.out.println(json); // 输出:{"name":"John","age":30,"address":"123 Street","nickname":"Johnny"}
}
}

在这个例子中:

  • Person 类包含一个 Map<String, Object> 来存储动态属性。
  • 使用 @JsonAnyGetter 标注 getAdditionalProperties() 方法,表示 additionalProperties 中的键值对应该被序列化为 JSON 字段。
  • 通过调用 addAdditionalProperty() 方法向 additionalProperties 中添加动态字段。

输出:

{
"name": "John",
"age": 30,
"address": "123 Street",
"nickname": "Johnny"
}

总结一下!

特别适合那些字段不固定、可能需要动态扩展的 JSON 数据结构,比如配置项、参数列表、插件信息等。

文章目录
  1. 1. 开家杂货铺吧
    1. 1.1. @JsonAnySetter:随便放!你给啥我都能接
    2. 1.2. @JsonAnyGetter:随便拿!需要啥我都能给
  2. 2. 总结一下!