JAVA JSON 输出属性丢失?@JsonProperty 与 @JsonSetter 配置讲解

JSON 输出属性丢失?@JsonProperty 与 @JsonSetter 配置讲解

大家好,今天我们来探讨一个在 Java JSON 处理中常见的困境:JSON 序列化(输出)时,某些属性意外丢失。我们将深入研究导致这种现象的原因,并重点讲解 Jackson 库中的 @JsonProperty@JsonSetter 注解,以及它们如何帮助我们解决这个问题。

问题复现:属性丢失的场景

在 Java 应用中,我们经常使用 JSON 作为数据交换的格式。Jackson 是一个流行的 JSON 处理库,它提供了强大的序列化和反序列化功能。然而,在使用 Jackson 的过程中,我们可能会遇到这样的情况:Java 对象中的某些属性在转换为 JSON 字符串时,并没有出现在输出结果中。

例如,考虑以下 User 类:

public class User {
    private String firstName;
    private String lastName;
    private int age;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + ''' +
                ", lastName='" + lastName + ''' +
                ", age=" + age +
                '}';
    }
}

如果我们使用 Jackson 将这个 User 对象序列化为 JSON,一切正常:

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setFirstName("John");
        user.setLastName("Doe");
        user.setAge(30);

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json); // 输出: {"firstName":"John","lastName":"Doe","age":30}
    }
}

但假设我们稍微修改一下 User 类,将 firstName 属性的 getter 方法命名为 getFname (注意 firstNamefname 的区别):

public class User {
    private String firstName;
    private String lastName;
    private int age;

    public String getFname() { // 修改了 getter 方法名
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + ''' +
                ", lastName='" + lastName + ''' +
                ", age=" + age +
                '}';
    }
}

再次运行相同的序列化代码:

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setFirstName("John");
        user.setLastName("Doe");
        user.setAge(30);

        ObjectMapper objectMapper = new ObjectMapper();
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json); // 输出: {"lastName":"Doe","age":30}  firstName 丢失了!
    }
}

可以看到,JSON 输出中 firstName 属性消失了。这就是我们今天要解决的问题。

属性丢失的原因:Jackson 的默认行为

Jackson 默认使用基于 JavaBeans 规范的属性发现机制。这意味着它通过查找符合 getXXX()isXXX() 模式的方法来确定哪些属性应该被序列化。

  • Getter 方法: Jackson 会查找以 get 开头,后跟大写字母的方法(如 getFirstName),或者对于 boolean 类型,以 is 开头,后跟大写字母的方法(如 isActive)。 Jackson会将getis后面的大写字母转换为小写,作为json的属性名。例如getFirstName对应firstName

  • Setter 方法: Jackson 会查找以 set 开头,后跟大写字母的方法(如 setFirstName)。

如果一个属性只有 setter 方法而没有 getter 方法,或者 getter 方法不符合命名规范,Jackson 默认不会将其序列化到 JSON 中。在上面的例子中,由于我们将 firstName 的 getter 方法命名为 getFnameJackson 无法识别它是一个标准的 getter 方法,因此忽略了 firstName 属性。

使用 @JsonProperty 解决属性丢失问题

@JsonProperty 注解允许我们显式地指定 JSON 属性的名称,并将其与 Java 类的字段或方法关联起来。 它可以作用在字段上,也可以作用在getter和setter方法上。

1. 在字段上使用 @JsonProperty

我们可以直接在 firstName 字段上使用 @JsonProperty 注解,指定它对应的 JSON 属性名称为 "firstName":

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
    @JsonProperty("firstName")
    private String firstName;
    private String lastName;
    private int age;

    public String getFname() { // 修改了 getter 方法名
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + ''' +
                ", lastName='" + lastName + ''' +
                ", age=" + age +
                '}';
    }
}

现在,即使 getter 方法的名称不符合规范,Jackson 也会根据 @JsonProperty 注解将 firstName 属性序列化到 JSON 中。

2. 在 Getter 方法上使用 @JsonProperty

另一种方法是在 getter 方法上使用 @JsonProperty 注解:

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {
    private String firstName;
    private String lastName;
    private int age;

    @JsonProperty("firstName")
    public String getFname() { // 修改了 getter 方法名
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + ''' +
                ", lastName='" + lastName + ''' +
                ", age=" + age +
                '}';
    }
}

这两种方法的效果是相同的。 推荐在getter方法上添加,这样可以更清晰地表达属性的序列化行为。

@JsonProperty 的作用

作用域 描述
字段 指定该字段对应的 JSON 属性名称。 即使没有标准的 getter 方法,Jackson 也会根据 @JsonProperty 注解将该字段序列化到 JSON 中.
Getter 指定该 getter 方法返回的值对应的 JSON 属性名称。 Jackson 会调用该 getter 方法获取属性值,并将其序列化到 JSON 中. 即使getter方法名称不符合规范,也会生效。
Setter 指定该 setter 方法用于设置哪个 JSON 属性的值。 Jackson 会使用该 setter 方法将 JSON 中的属性值设置到 Java 对象的相应字段中。 即使setter方法名称不符合规范,也会生效。 如果只添加了setter的JsonProperty,而没有getter的JsonProperty,那么序列化的时候,这个属性依然不会出现在json中。反序列化时会生效。

使用 @JsonSetter 进行反序列化配置

@JsonSetter 注解主要用于反序列化,它允许我们指定在反序列化 JSON 数据时,哪个 setter 方法应该用于设置特定的 JSON 属性值。 当setter方法名称不符合规范时,这个注解非常有用。

例如,假设我们接收到的 JSON 数据中,firstName 属性的名称是 "fname",而不是 "firstName"。 我们的 User 类的 setFirstName 方法仍然使用 "firstName" 作为参数名。 我们可以使用 @JsonSetter 注解来解决这个问题:

import com.fasterxml.jackson.annotation.JsonSetter;

public class User {
    private String firstName;
    private String lastName;
    private int age;

    public String getFname() {
        return firstName;
    }

    @JsonSetter("fname")
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + firstName + ''' +
                ", lastName='" + lastName + ''' +
                ", age=" + age +
                '}';
    }
}

现在,当我们从 JSON 字符串反序列化 User 对象时,Jackson 会将 "fname" 属性的值传递给 setFirstName 方法。

@JsonSetter 的作用

作用域 描述
Setter 指定该 setter 方法用于设置哪个 JSON 属性的值。 Jackson 会使用该 setter 方法将 JSON 中的属性值设置到 Java 对象的相应字段中。 即使setter方法名称不符合规范,也会生效。

@JsonProperty 与 @JsonSetter 的联合使用

在一些复杂的场景中,我们可能需要同时使用 @JsonProperty@JsonSetter 注解。例如,我们可能需要将 Java 类的属性名与 JSON 属性名完全解耦,并且 getter 和 setter 方法的命名都不符合 JavaBeans 规范。

考虑以下情况:

  • Java 类中的属性名为 internalFirstName
  • JSON 中的属性名为 first_name
  • getter 方法名为 getFirstNameValue
  • setter 方法名为 setTheFirstName

我们可以这样配置:

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;

public class User {
    private String internalFirstName;
    private String lastName;
    private int age;

    @JsonProperty("first_name")
    public String getFirstNameValue() {
        return internalFirstName;
    }

    @JsonSetter("first_name")
    public void setTheFirstName(String firstName) {
        this.internalFirstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "firstName='" + internalFirstName + ''' +
                ", lastName='" + lastName + ''' +
                ", age=" + age +
                '}';
    }
}

在这个例子中,@JsonProperty("first_name") 注解确保 getFirstNameValue 方法返回的值在序列化为 JSON 时,对应的属性名为 "first_name"。@JsonSetter("first_name") 注解确保在反序列化 JSON 数据时,"first_name" 属性的值会被传递给 setTheFirstName 方法。

总结:理解 Jackson 属性映射机制并灵活使用注解

总而言之,Jackson 默认使用 JavaBeans 规范进行属性发现。如果属性的 getter 或 setter 方法不符合规范,或者我们需要自定义 JSON 属性名,可以使用 @JsonProperty@JsonSetter 注解来显式地指定属性映射关系。 理解 Jackson 的属性映射机制,并灵活使用这两个注解,可以帮助我们有效地解决 JSON 序列化和反序列化过程中遇到的属性丢失问题,并实现更灵活的数据绑定。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注