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

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

各位朋友,大家好!今天我们来聊聊在使用 Jackson 库进行 Java 对象和 JSON 字符串相互转换时,经常会遇到的一个问题:JSON 输出属性丢失。这个问题往往是因为序列化和反序列化过程中,属性名不匹配,或者 Jackson 无法正确识别属性而导致的。我们将深入探讨这个问题,并重点讲解 @JsonProperty@JsonSetter 这两个注解的用法,帮助大家更好地控制 JSON 的序列化和反序列化过程。

问题背景:JSON 序列化与反序列化

在微服务架构和前后端分离的开发模式中,JSON 作为一种通用的数据交换格式,被广泛应用于各种场景。Java 对象和 JSON 字符串之间的转换,是开发过程中必不可少的一环。Jackson 是一个非常流行的 Java JSON 处理库,它提供了简单易用的 API,可以将 Java 对象序列化成 JSON 字符串,也可以将 JSON 字符串反序列化成 Java 对象。

然而,在使用 Jackson 的过程中,我们可能会遇到一些问题,比如 JSON 输出的属性缺失。这通常是因为 Jackson 默认采用基于 Bean Properties 的方式进行序列化和反序列化。也就是说,它会查找 Java 类的 getter 和 setter 方法来确定哪些属性需要序列化和反序列化。如果属性名不匹配,或者缺少 getter/setter 方法,Jackson 就可能无法正确识别属性,从而导致 JSON 输出属性丢失。

属性丢失的常见原因

  1. 属性名不匹配: JSON 字符串中的属性名与 Java 类的属性名不一致。例如,JSON 字符串中是 user_name,而 Java 类中是 userName

  2. 缺少 Getter/Setter 方法: Jackson 默认通过 getter 方法来获取属性值进行序列化,通过 setter 方法来设置属性值进行反序列化。如果缺少 getter 或 setter 方法,Jackson 就无法正确处理该属性。

  3. 属性访问权限限制: 如果属性的访问权限是 private,并且没有提供对应的 getter 和 setter 方法,Jackson 也无法访问该属性。

  4. 忽略属性: 有些属性可能不需要序列化或反序列化,但 Jackson 默认会处理所有符合 Bean Properties 规范的属性。

  5. 命名冲突: 父类和子类可能存在同名属性,导致序列化冲突。

@JsonProperty 注解:显式指定 JSON 属性名

@JsonProperty 注解是解决 JSON 输出属性丢失问题的关键。它可以显式地指定 Java 类的属性与 JSON 字符串中的属性名之间的映射关系。

用法:

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {

    @JsonProperty("user_name")
    private String userName;

    private int age;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getAge() {
        return age;
    }

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

说明:

  • @JsonProperty("user_name")userName 属性映射到 JSON 字符串中的 user_name 属性。
  • 在序列化时,userName 属性的值会输出到 JSON 字符串的 user_name 属性中。
  • 在反序列化时,JSON 字符串中的 user_name 属性的值会赋值给 userName 属性。

示例:

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonPropertyExample {

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

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

        String jsonInput = "{"user_name":"Jane Doe","age":25}";
        User newUser = objectMapper.readValue(jsonInput, User.class);
        System.out.println(newUser.getUserName()); // 输出: Jane Doe
        System.out.println(newUser.getAge()); // 输出: 25
    }
}

@JsonProperty 的参数:

@JsonProperty 注解除了可以指定属性名之外,还可以设置其他参数,来控制序列化和反序列化的行为。

参数 类型 描述
value String JSON 属性名。
required boolean 是否是必需的属性。如果设置为 true,则在反序列化时,如果 JSON 字符串中缺少该属性,会抛出异常。
index int 序列化时属性的顺序。可以通过设置 index 来控制属性在 JSON 字符串中的排列顺序。
defaultValue String 属性的默认值。如果在反序列化时,JSON 字符串中缺少该属性,则会将 defaultValue 的值赋给该属性。
access Access 定义属性的访问方式。 Access.AUTO (默认): Jackson 自动检测 getter/setter 方法。 Access.READ_ONLY: 只能序列化,不能反序列化。 Access.WRITE_ONLY: 只能反序列化,不能序列化。 Access.READ_WRITE: 既可以序列化,也可以反序列化。

示例(required):

import com.fasterxml.jackson.annotation.JsonProperty;

public class Product {

    @JsonProperty(value = "product_id", required = true)
    private String productId;

    private String productName;

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }
}

如果反序列化的 JSON 字符串中缺少 product_id 属性,则会抛出 MismatchedInputException 异常。

示例(index):

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {

    @JsonProperty(index = 2)
    private String lastName;

    @JsonProperty(index = 1)
    private String firstName;

    @JsonProperty(index = 0)
    private int age;

    public String getLastName() {
        return lastName;
    }

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

    public String getFirstName() {
        return firstName;
    }

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

    public int getAge() {
        return age;
    }

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

序列化后的 JSON 字符串中,属性的顺序将是 age, firstName, lastName

@JsonSetter 注解:指定 Setter 方法的属性名

@JsonSetter 注解用于指定 Setter 方法对应的 JSON 属性名。当 JSON 字符串中的属性名与 Java 类的属性名不一致时,可以使用 @JsonSetter 注解来建立映射关系。

用法:

import com.fasterxml.jackson.annotation.JsonSetter;

public class Employee {

    private String employeeName;

    private int employeeAge;

    @JsonSetter("emp_name")
    public void setEmployeeName(String employeeName) {
        this.employeeName = employeeName;
    }

    public String getEmployeeName() {
        return employeeName;
    }

    public int getEmployeeAge() {
        return employeeAge;
    }

    public void setEmployeeAge(int employeeAge) {
        this.employeeAge = employeeAge;
    }
}

说明:

  • @JsonSetter("emp_name")setEmployeeName 方法与 JSON 字符串中的 emp_name 属性关联起来。
  • 在反序列化时,JSON 字符串中的 emp_name 属性的值会通过 setEmployeeName 方法赋值给 employeeName 属性。

示例:

import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonSetterExample {

    public static void main(String[] args) throws Exception {
        String jsonInput = "{"emp_name":"Alice","employeeAge":28}";

        ObjectMapper objectMapper = new ObjectMapper();
        Employee employee = objectMapper.readValue(jsonInput, Employee.class);

        System.out.println(employee.getEmployeeName()); // 输出: Alice
        System.out.println(employee.getEmployeeAge()); // 输出: 28
    }
}

@JsonProperty vs @JsonSetter

  • @JsonProperty 注解通常用于属性的字段上,用于同时控制序列化和反序列化。
  • @JsonSetter 注解用于 setter 方法上,主要用于反序列化,指定哪个 JSON 属性对应于该 setter 方法。

一般来说,推荐使用 @JsonProperty 注解,因为它更加简洁和直观。只有在特殊情况下,例如需要对 setter 方法进行自定义处理时,才需要使用 @JsonSetter 注解。

属性忽略

有时候,我们可能需要忽略某些属性,不让它们参与序列化或反序列化。Jackson 提供了多种方式来忽略属性:

  1. @JsonIgnore 忽略单个属性。
  2. @JsonIgnoreProperties 忽略多个属性。
  3. @JsonIgnoreType 忽略某种类型的属性。

@JsonIgnore 用法:

import com.fasterxml.jackson.annotation.JsonIgnore;

public class SensitiveData {

    private String publicData;

    @JsonIgnore
    private String sensitiveInfo;

    public String getPublicData() {
        return publicData;
    }

    public void setPublicData(String publicData) {
        this.publicData = publicData;
    }

    public String getSensitiveInfo() {
        return sensitiveInfo;
    }

    public void setSensitiveInfo(String sensitiveInfo) {
        this.sensitiveInfo = sensitiveInfo;
    }
}

sensitiveInfo 属性会被忽略,不会出现在 JSON 字符串中。

@JsonIgnoreProperties 用法:

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties({"password", "ssn"})
public class UserProfile {

    private String username;

    private String password;

    private String ssn;

    private String email;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSsn() {
        return ssn;
    }

    public void setSsn(String ssn) {
        this.ssn = ssn;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

passwordssn 属性会被忽略。 @JsonIgnoreProperties(ignoreUnknown = true) 可以忽略JSON字符串中未知的属性,反序列化时不会报错。

@JsonIgnoreType 用法:

import com.fasterxml.jackson.annotation.JsonIgnoreType;

@JsonIgnoreType
public class Address {
    private String street;
    private String city;

    // Getters and setters
}

public class Employee {
    private String name;
    private Address address; // Address 类型会被忽略

    // Getters and setters
}

Address 类型的属性会被忽略。

解决命名冲突

当父类和子类存在同名属性时,可能会导致序列化冲突。可以使用 @JsonManagedReference@JsonBackReference 注解来解决这个问题。

示例:

import com.fasterxml.jackson.annotation.JsonManagedReference;
import com.fasterxml.jackson.annotation.JsonBackReference;
import java.util.List;
import java.util.ArrayList;

public class Category {

    private int id;
    private String name;

    @JsonManagedReference
    private List<Product> products = new ArrayList<>();

    // Getters and setters
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products = products;
    }
}

class Product {
    private int id;
    private String name;

    @JsonBackReference
    private Category category;

    // Getters and setters
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }
}

@JsonManagedReference 注解用于父类(Category)的 products 属性上,表示该属性是关系的所有者,负责序列化关联对象。 @JsonBackReference 注解用于子类(Product)的 category 属性上,表示该属性是关系的被引用者,在序列化时会被忽略,避免循环引用。

其他一些技巧和注意事项

  • 使用 Lombok: Lombok 可以自动生成 getter 和 setter 方法,减少代码量。
  • 配置 ObjectMapper: 可以通过配置 ObjectMapper 对象来定制序列化和反序列化的行为,例如设置日期格式、忽略空值等。
  • 自定义序列化器和反序列化器: 如果需要对某些属性进行特殊的序列化和反序列化处理,可以自定义序列化器和反序列化器。
  • 版本兼容性: 在升级 Jackson 版本时,需要注意版本兼容性问题。

灵活运用注解,精准控制JSON输出

总结一下,解决 Java JSON 输出属性丢失的问题,关键在于理解 Jackson 的序列化和反序列化机制,并灵活运用 @JsonProperty@JsonSetter 注解,显式地指定属性名映射关系。同时,还可以使用 @JsonIgnore 等注解来忽略不需要序列化或反序列化的属性。通过这些方法,我们可以更好地控制 JSON 的序列化和反序列化过程,确保数据的正确性和完整性。

发表回复

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