Skip to content
xiaoyang 的头像xiaoyang发布于 2025-02-2811,256 字阅读约 38 分钟浏览加载中

JSON 序列化与反序列 ​

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于 Web 应用程序的数据传输。在 Java 开发中,我们通常需要将 Java 对象与 JSON 进行相互转换(序列化和反序列化)。Java 提供了多种 JSON 解析库,最常用的包括 Jackson、FastJSON 和 Gson。这些库在实现细节和性能上有所不同。

1. JSON 序列化与反序列化概述 ​

序列化(Serialization) ​

序列化是指将 Java 对象转化为 JSON 格式的过程。在这个过程中,对象的字段和值会被转换成 JSON 字符串。常见的序列化操作包括:

  • Java 对象到 JSON 字符串
  • Java 对象到 JSON 文件

反序列化(Deserialization) ​

反序列化是指将 JSON 格式的数据转化为 Java 对象的过程。在这个过程中,JSON 字符串会被解析并映射回 Java 对象的字段。常见的反序列化操作包括:

  • JSON 字符串到 Java 对象
  • JSON 文件到 Java 对象

2. Jackson 序列化与反序列化 ​

Jackson 是最常用的 JSON 解析库之一,它提供了强大的功能,支持高级特性如流式处理、多态类型支持以及对象映射等。Jackson 的核心组件是 ObjectMapper 类,它支持将 Java 对象与 JSON 数据相互转换。

2.1 Jackson 序列化(对象 → JSON) ​

Jackson 默认使用 getter 方法 和 字段名 来进行序列化。它会根据 JavaBean 规范,查找 getter 方法或字段,并将其转化为 JSON 对象中的键值对。

示例

java
import com.fasterxml.jackson.databind.ObjectMapper;

class User {
    private String name;
    private int age;

    // 无参构造方法
    public User() {}

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

public class JacksonSerializationExample {
    public static void main(String[] args) throws Exception {
        User user = new User("Alice", 25);
        
        // 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 序列化 Java 对象为 JSON 字符串
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json);  // 输出:{"name":"Alice","age":25}
    }
}

Jackson 序列化规则

  • 默认序列化字段名是 Java 类中的字段名。
  • 如果字段有 get 方法,优先使用 getter 方法。
  • 支持 @JsonProperty 注解重命名字段。
  • 可以使用 @JsonIgnore 注解忽略某些字段。

2.2 Jackson 反序列化(JSON → 对象) ​

Jackson 反序列化时,会通过字段名和类型信息将 JSON 数据映射为相应的 Java 对象。如果 Java 类的字段没有对应的 JSON 键名,Jackson 会根据字段类型进行匹配。

示例

java
public class JacksonDeserializationExample {
    public static void main(String[] args) throws Exception {
        String json = "{\"name\":\"Alice\",\"age\":25}";
        
        // 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();
        
        // 反序列化 JSON 字符串为 Java 对象
        User user = objectMapper.readValue(json, User.class);
        System.out.println(user.getName());  // 输出:Alice
    }
}

Jackson 反序列化规则

  • 默认依赖无参构造方法。
  • 如果 Java 类没有无参构造方法,可以使用 @JsonCreator 注解指定构造方法。
  • 会根据字段名、@JsonProperty 和类型自动匹配 JSON 数据。

3. FastJSON 序列化与反序列化 ​

FastJSON 是由阿里巴巴开源的 JSON 解析库,目标是提供高性能的 JSON 序列化和反序列化。它的解析速度比其他库更快,尤其在大数据量的情况下,性能优势更为明显。

3.1 FastJSON 序列化(对象 → JSON) ​

FastJSON 的序列化机制与 Jackson 类似,默认使用 getter 方法 来获取字段值。如果没有 getter 方法,则直接访问字段。它也支持注解来定制序列化规则。

示例

java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.annotation.JSONField;

class User {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    @JSONField(name = "user_age")  // 自定义 JSON 字段名
    public int getAge() {
        return age;
    }
}

public class FastJsonSerializationExample {
    public static void main(String[] args) {
        User user = new User("Bob", 30);
        
        // 序列化 Java 对象为 JSON 字符串
        String json = JSON.toJSONString(user);
        System.out.println(json);  // 输出:{"name":"Bob","user_age":30}
    }
}

FastJSON 序列化规则

  • 默认使用 getter 方法进行序列化。
  • 可以使用 @JSONField(name = "xxx") 来重命名 JSON 字段。
  • 支持通过 @JSONField(serialize = false) 忽略字段。

3.2 FastJSON 反序列化(JSON → 对象) ​

FastJSON 会根据 JSON 数据的键值与 Java 类中的字段进行映射。它会优先调用 setter 方法 来赋值。如果没有 setter 方法,FastJSON 可能会直接通过反射访问字段。

示例

java
public class FastJsonDeserializationExample {
    public static void main(String[] args) {
        String json = "{\"name\":\"Bob\",\"user_age\":30}";
        
        // 反序列化 JSON 字符串为 Java 对象
        User user = JSON.parseObject(json, User.class);
        System.out.println(user.getName());  // 输出:Bob
    }
}

FastJSON 反序列化规则

  • 默认依赖无参构造方法。
  • 优先调用 setter 方法进行赋值。
  • 支持通过注解 @JSONField 来定制字段名。

4. Gson 序列化与反序列化 ​

Gson 是 Google 提供的 JSON 解析库,广泛应用于 Android 开发。Gson 的特点是轻量级、易用,并且支持简单的 JSON 解析和序列化操作。

4.1 Gson 序列化(对象 → JSON) ​

Gson 的序列化方式与 FastJSON 类似,它默认通过 字段访问 来进行序列化。Gson 会直接访问 Java 对象的字段,无需依赖 getter 方法。如果需要,可以使用 @SerializedName 注解来重命名 JSON 字段。

示例

java
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;

class User {
    @SerializedName("user_name")
    private String name;
    private int age;

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

public class GsonSerializationExample {
    public static void main(String[] args) {
        User user = new User("Charlie", 28);
        
        // 创建 Gson 实例
        Gson gson = new Gson();
        
        // 序列化 Java 对象为 JSON 字符串
        String json = gson.toJson(user);
        System.out.println(json);  // 输出:{"user_name":"Charlie","age":28}
    }
}

Gson 序列化规则

  • 默认直接访问字段进行序列化。
  • 使用 @SerializedName 注解来映射 JSON 字段名。
  • 如果字段被 transient 修饰,它将被忽略。

4.2 Gson 反序列化(JSON → 对象) ​

Gson 的反序列化过程相对简单,它直接通过 字段 将 JSON 数据映射到 Java 对象的字段。如果字段没有 setter 方法,Gson 仍然可以通过直接访问字段来完成反序列化。

示例

java
public class GsonDeserializationExample {
    public static void main(String[] args) {
        String json = "{\"user_name\":\"Charlie\",\"age\":28}";
        
        // 创建 Gson 实例
        Gson gson = new Gson();
        
        // 反序列化 JSON 字符串为 Java 对象
        User user = gson.fromJson(json, User.class);
        System.out.println(user.getName());  // 输出:Charlie
    }
}

Gson 反序列化规则

  • 直接访问字段,支持 @SerializedName 映射字段名。
  • 无需 setter 方法,也无需构造方法(但是无参构造方法推荐存在)。

5. 对比:Jackson、FastJSON 和 Gson ​

特性JacksonFastJSONGson
性能中等,适合一般应用高性能,特别在大数据量下更快性能较差,适合小型应用
序列化机制默认使用 Getter/Setter默认使用 Getter/Setter,支持注解默认通过字段访问,支持注解
反序列化机制依赖无参构造方法,支持注解依赖无参构造方法,支持注解直接访问字段,支持无参构造方法
注解支持支持丰富的注解,如 @JsonProperty支持 @JSONField,功能相对较少支持 @SerializedName 注解
安全性高,广泛使用且社区支持良好曾有安全漏洞,但最新版修复较好高,Google 提供的成熟解决方案
适用场景企业级应用,复杂数据结构处理高并发环境,大数据处理Android 开发、轻量级应用

6. JackJSON的Mixin机制 ​

Jackson是一个广泛用于Java的JSON处理库。Mixin注释(混入)是一种机制,允许你为类添加Jackson注释,而无需修改类的源代码。这在处理第三方类(你无法更改其代码)或保持领域模型干净(不包含序列化特定的注释)时非常有用。

6.1 使用场景? ​

Mixin注释特别适用于以下场景:

  • 第三方类:当你使用外部库的类时(无默认无参构造就会报错,没有get方法无法获取属性),无法直接修改它们,但需要添加Jackson注释。
  • 清洁代码:保持领域模型专注于业务逻辑,而不是序列化细节。
  • 自定义处理:可以自定义类的序列化和反序列化行为,而不更改其定义。

6.2 如何使用Mixin注释 ​

使用Mixin注释的步骤包括:

  1. 创建Mixin类:这是一个抽象类或接口,包含目标类的相同方法签名,并添加所需的Jackson注释。
  2. 注册Mixin:通过配置ObjectMapper实例,告诉Jackson使用这个Mixin类为目标类。

6.3 定制化字段序列化和反序列化 ​

在某些场景下,我们希望对某个类的某些字段进行特殊处理,比如忽略某些字段、重命名字段,或者根据不同的条件序列化不同的字段。通过 Mixin,我们可以方便地对特定字段应用 Jackson 的注解,而无需修改原始类的代码。

例子:根据条件序列化字段 ​

假设我们有以下类,其中包含多个字段,但我们希望根据某些条件(例如某个特定的标志)来序列化或忽略某些字段:

java
public class User {
    private String username;
    private String password;
    private boolean isAdmin;
    
    // Getter and Setter
}

我们想要在某些情况下忽略 password 字段,并且在序列化时将 isAdmin 字段根据其值转换为不同的字符串(比如 true 转换为 "Yes",false 转换为 "No")。

使用 Mixin 对字段进行定制 ​

通过 Mixin,我们可以通过 @JsonIgnore、@JsonProperty 等注解来定制字段的序列化行为。

java
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

public abstract class UserMixin {

    @JsonIgnore
    private String password;

    @JsonProperty("isAdminStatus")
    public String getAdminStatus() {
        return isAdmin() ? "Yes" : "No";
    }
}

在上面的 UserMixin 中,我们做了以下事情:

  • 使用 @JsonIgnore 注解忽略 password 字段,这意味着该字段在序列化时不会被输出。
  • 使用 @JsonProperty 自定义了 getAdminStatus 方法,以将 isAdmin 字段的值转换为 "Yes" 或 "No"。

注册 Mixin ​

java
ObjectMapper objectMapper = new ObjectMapper();

// 注册 Mixin
objectMapper.addMixIn(User.class, UserMixin.class);

// 创建 User 对象
User user = new User();
user.setUsername("john_doe");
user.setPassword("12345");
user.setAdmin(true);

// 序列化 User 对象
String json = objectMapper.writeValueAsString(user);
System.out.println(json);

序列化结果:

json
{
  "username": "john_doe",
  "isAdminStatus": "Yes"
}

你是对的!在处理复杂的多态类型时,Jackson 的 @JsonTypeInfo 和 @JsonSubTypes 注解应该结合类型信息字段来实现自动转换。这是关键的部分,我们通常需要确保 JSON 中包含一个标识字段(比如 messageType),然后根据该字段的值来确定应该反序列化为哪个具体的子类。这是处理多态类型时的标准做法。

6.4 处理复杂的多态类型 ​

Jackson 在处理多态类型时,通常会依赖某个字段(如 type、messageType 等)来保存类型信息。这种字段允许 Jackson 在序列化和反序列化时根据该字段的值来动态地确定应该转换为哪个子类。

例子:多层次的继承体系 ​

假设我们有以下的继承体系:

java
public abstract class Message {
    private String messageType;
  	private String text;
    // Getter and Setter
}

public class AssistantMessage extends Message {
    private String assistantDetail;
    // Getter and Setter
}

public class UserMessage extends Message {
    private String userMessageDetail;
    // Getter and Setter
}

在这个例子中,Message 是父类,而 AssistantMessage、UserMessage 都是它的子类。我们希望根据每个消息的具体类型(比如 "ASSISTANT"、"USER")来反序列化为相应的子类。

关键点:添加类型信息字段 ​

我们将通过 @JsonTypeInfo 注解来指示 Jackson 在序列化时将类型信息存储在 messageType 字段中,并通过 @JsonSubTypes 注解来定义不同子类的映射。

我们可以在 MessageMixin 类中配置这些注解,以确保 Jackson 能正确地处理多态类型。

java
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,       // 使用类名作为标识符
        include = JsonTypeInfo.As.PROPERTY,  // 类型信息作为属性
        property = "messageType",        // 类型信息存储在 "messageType" 字段中
        visible = true                   // 确保 "messageType" 字段出现在序列化的结果中
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = AssistantMessage.class, name = "ASSISTANT"),
        @JsonSubTypes.Type(value = UserMessage.class, name = "USER"),
        @JsonSubTypes.Type(value = UserMessage.class, name = "SYSTEM")
})
public abstract class MessageMixin {
}
  • @JsonTypeInfo:配置 Jackson 在序列化时将类型信息保存在 messageType 字段中,这样我们就可以在反序列化时根据该字段的值来确定具体的子类。
  • @JsonSubTypes:将具体的子类与类型标识符(如 "ASSISTANT")关联起来。这允许 Jackson 根据 messageType 字段的值来选择正确的类。

注册 Mixin ​

接下来,我们使用 ObjectMapper 将这个 MessageMixin 注册到 Message 类中。

java
import com.fasterxml.jackson.databind.ObjectMapper;

public class MixinExample {
    public static void main(String[] args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();

        // 注册 Mixin 类
        objectMapper.addMixIn(Message.class, MessageMixin.class);
				objectMapper.addMixIn(AssistantMessage.class, AssistantMessageMixin.class);
      	objectMapper.addMixIn(UserMessage.class, UserMessageMixin.class);
        // 创建具体的 Message 类型
        Message message = new AssistantMessage();
        message.setText("Hello, how can I assist you today?");

        // 序列化 Message 对象
        String json = objectMapper.writeValueAsString(message);
        System.out.println("Serialized JSON: " + json);

        // 反序列化 JSON 为 Message 类型
        String inputJson = "{\"messageType\":\"ASSISTANT\",\"content\":\"Hello, how can I assist you today?\";
        Message deserializedMessage = objectMapper.readValue(inputJson, Message.class);
        System.out.println("Deserialized Message: " + deserializedMessage.getClass().getName());
    }
}

序列化结果 ​

当我们序列化一个 AssistantMessage 对象时,messageType 字段将包含类型标识符:

json
{
  "messageType": "ASSISTANT",
  "text": "Hello, how can I assist you today?",
}

反序列化结果 ​

根据 JSON 中的 messageType 字段,Jackson 会自动反序列化为相应的子类。在这个例子中,"messageType": "ASSISTANT" 会将 JSON 数据反序列化为 AssistantMessage 类的实例。

java
Deserialized Message: AssistantMessage

关键点总结 ​

  1. 类型信息字段:在多态类型的处理中,@JsonTypeInfo 注解指定了一个字段(如 messageType)来存储类型信息,以便反序列化时确定具体的子类。
  2. @JsonSubTypes 注解:使用该注解将每个子类与类型标识符(如 "ASSISTANT", "USER")关联起来,这样 Jackson 就能根据字段的值来正确地选择子类。
  3. Mixin 的作用:通过 Mixin,我们可以为现有的类(如 Message)动态添加类型信息和子类映射,而不需要修改原有代码。

6.5 自定义序列化器 ​

假设我们有一个 Date 类型字段,我们希望将其序列化为特定的格式(例如 yyyy-MM-dd)。我们可以编写一个自定义的序列化器,并通过 Mixin 将其应用到字段上。

java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomDateSerializer extends JsonSerializer<Date> {
    @Override
    public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        gen.writeString(dateFormat.format(value));
    }
}

在 Mixin 中应用自定义序列化器 ​

java
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public abstract class MessageMixin {
    
    @JsonSerialize(using = CustomDateSerializer.class)
    private Date timestamp;
}

注册自定义序列化器 ​

java
ObjectMapper objectMapper = new ObjectMapper();

// 注册 Mixin
objectMapper.addMixIn(Message.class, MessageMixin.class);

// 创建一个 Message 对象
Message message = new Message();
message.setTimestamp(new Date());

// 序列化 Message 对象
String json = objectMapper.writeValueAsString(message);
System.out.println(json);

6.7 总结 ​

Jackson 的 Mixin 机制是一个非常强大的工具,允许我们在不修改原始类代码的情况下,动态地为类添加序列化和反序列化的行为。通过 Mixin,我们不仅可以处理多态类型,还能够定制字段的序列化方式、忽略字段、应用自定义序列化器,以及处理复杂的继承体系。

结合 Jackson 的其他功能,如 @JsonTypeInfo、@JsonSubTypes、@JsonSerialize 等,Mixin 机制能够大大提高灵活性,使得序列化和反序列化操作更加高效和可定制。

DISCUSSION

评论区

欢迎分享你的思考、补充或问题。

基于 VitePress 搭建