程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Java常用开发工具包

    • Jackson(JSON处理库)
      • 一、Jackson 依赖配置与基础环境
      • 二、Java 对象序列化为 JSON(对象转 JSON)
      • 三、JSON 反序列化为 Java 对象(JSON 转对象)
      • 四、树模型处理复杂 JSON(JsonNode 操作)
        • 1. 基础 JsonNode 操作
        • 2. 处理复杂JSON数据结构
      • 五、集合类型与 JSON 转换(List/Map 操作)
        • 1. JSON 数组 List 对象
        • 2. JSON 对象 Map
      • 六、JSON 格式化与自定义配置
      • 七、常见 Jackson 注解使用
    • FastJson2(JSON处理库)
    • Gson(JSON处理库)
    • BeanUtils(对象复制工具)
    • MapStruct(对象转换工具)
    • Guava(开发工具包)
    • ThreadLocal(本地线程变量)
    • SLF4j(日志框架)
    • Lombok (注解插件)
  • 开发工具包
  • Java常用开发工具包
scholar
2024-08-20
目录

Jackson(JSON处理库)

# Jackson(JSON处理库)

前言

Jackson 是 Java 生态系统中最流行的高性能 JSON 处理库之一,也是 Spring Boot 框架默认集成的 JSON 解决方案。它以其强大的功能、灵活性和出色的性能而闻名。Jackson 不仅支持基础的 Java 对象与 JSON 字符串之间的相互转换,还提供了强大的 JsonNode 树模型用于处理复杂或结构不定的 JSON 数据,以及丰富的注解来精细控制序列化和反序列化的行为。本篇文档将详细介绍 Jackson 的核心用法、高级特性以及在 Spring Boot 项目中的应用。

# 一、Jackson 依赖配置与基础环境

在标准的 Spring Boot 项目中,spring-boot-starter-web 或 spring-boot-starter-json 会自动包含 Jackson 的核心依赖 jackson-databind。因此,通常情况下,你无需手动添加任何 Jackson 相关的依赖。

如果你是在非 Spring Boot 环境下使用 Jackson,或者需要显式管理依赖版本,可以通过 Maven 或 Gradle 添加:

Maven 配置:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <!-- 建议使用最新稳定版本 -->
    <!-- <version>2.15.3</version> -->
</dependency>
1
2
3
4
5
6

说明:jackson-databind 依赖会自动传递包含 jackson-core(提供基础的流式 API 和核心抽象)和 jackson-annotations(提供标准注解)。这三个库构成了 Jackson 的核心功能集。

# 二、Java 对象序列化为 JSON(对象转 JSON)

序列化是指将 Java 对象的状态信息转换为可以存储或传输的格式(在这里是 JSON 字符串)的过程。Jackson 的核心类 ObjectMapper 负责执行此操作。

核心类:ObjectMapper

ObjectMapper 是 Jackson 库的主入口点,用于读写 JSON。它是线程安全的,因此可以全局共享和重用一个实例以提高性能。

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
// import java.io.File; // 如果需要写入文件,取消注释
// import java.io.IOException; // 如果需要写入文件,取消注释

/**
 * 演示如何使用 Jackson 将 Java 对象序列化为 JSON 字符串
 */
public class JacksonObjectToJsonExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        // ObjectMapper 是线程安全的,可以在应用中共享和重用
        ObjectMapper objectMapper = new ObjectMapper();

        // 2. 创建一个需要序列化的 Java 对象 (POJO)
        User user = new User("张三", 25);

        try {
            // 3. 使用 writeValueAsString() 方法将对象转换为 JSON 字符串
            String jsonString = objectMapper.writeValueAsString(user);

            // 4. 输出生成的 JSON 字符串
            System.out.println("Java 对象序列化为 JSON 字符串: \n" + jsonString);

            // 5. (可选) 使用 writeValue() 方法将对象序列化并写入文件
            // try {
            //     objectMapper.writeValue(new File("user.json"), user);
            //     System.out.println("JSON 已成功写入到 user.json 文件");
            // } catch (IOException ioException) {
            //     System.err.println("写入 JSON 文件时出错: " + ioException.getMessage());
            // }

        } catch (JsonProcessingException e) {
            // 处理序列化过程中可能发生的异常
            System.err.println("将对象序列化为 JSON 时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

/**
 * 用户实体类 (Plain Old Java Object - POJO)
 * Jackson 序列化/反序列化需要此类
 */
class User {
    private String name; // 用户名
    private int age;     // 年龄

    // Jackson 反序列化通常需要一个无参构造函数
    public User() {
    }

    // 用于创建 User 对象的有参构造函数
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter 方法:Jackson 序列化时通过 Getter 方法访问属性值
    public String getName() {
        return name;
    }

    // Setter 方法:Jackson 反序列化时通过 Setter 方法设置属性值
    public void setName(String name) {
        this.name = name;
    }

    // Getter for age
    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "User{" +
               "name='" + name + '\'' +
               ", age=" + age +
               '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

输出结果:

Java 对象序列化为 JSON 字符串:
{"name":"张三","age":25}
1
2

核心API说明:

  • ObjectMapper():构造函数,创建一个新的 ObjectMapper 实例。
  • writeValueAsString(Object value):将给定的 Java 对象序列化为 JSON 格式的字符串。如果对象为 null,默认会序列化为 JSON null。
  • writeValue(File resultFile, Object value):将给定的 Java 对象序列化为 JSON,并将结果写入指定的文件。
  • writeValue(OutputStream out, Object value):将 Java 对象序列化为 JSON 并写入输出流。
  • writeValue(Writer w, Object value):将 Java 对象序列化为 JSON 并写入 Writer。

# 三、JSON 反序列化为 Java 对象(JSON 转对象)

反序列化是序列化的逆过程,指将 JSON 字符串解析并转换回 Java 对象。ObjectMapper 同样负责此任务。

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
// import java.io.File; // 如果需要从文件读取,取消注释
// import java.io.IOException; // 如果需要从文件读取,取消注释

/**
 * 演示如何使用 Jackson 将 JSON 字符串反序列化为 Java 对象
 */
public class JacksonJsonToObjectExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 2. 准备一个待解析的 JSON 字符串
        String jsonString = "{\"name\":\"张三\",\"age\":25}";
        // 注意:JSON 字符串中的 key 必须与 User 类中的属性名(或通过注解指定的名称)匹配

        try {
            // 3. 使用 readValue() 方法将 JSON 字符串转换为指定类型的 Java 对象
            // 第一个参数是 JSON 输入源 (字符串、文件、URL、输入流等)
            // 第二个参数是目标 Java 类的 Class 对象
            User user = objectMapper.readValue(jsonString, User.class);

            // 4. 输出反序列化后的 Java 对象信息
            System.out.println("JSON 字符串成功反序列化为 User 对象:");
            System.out.println("- 用户名: " + user.getName());
            System.out.println("- 年龄: " + user.getAge());
            System.out.println("- 对象详情: " + user.toString());

            // 5. (可选) 从文件读取 JSON 并反序列化
            // try {
            //     User userFromFile = objectMapper.readValue(new File("user.json"), User.class);
            //     System.out.println("\n从文件 user.json 反序列化成功:");
            //     System.out.println("- 文件用户名: " + userFromFile.getName());
            //     System.out.println("- 文件年龄: " + userFromFile.getAge());
            // } catch (IOException ioException) {
            //     System.err.println("\n从 JSON 文件读取或反序列化时出错: " + ioException.getMessage());
            // }

        } catch (JsonProcessingException e) {
            // 处理 JSON 解析或绑定过程中可能发生的异常
            System.err.println("将 JSON 反序列化为对象时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
    // User 类定义同上一个示例
}

// User 类定义 (需要与上一个示例中的 User 类保持一致)
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; }

    @Override
    public String toString() {
        return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

输出结果:

JSON 字符串成功反序列化为 User 对象:
- 用户名: 张三
- 年龄: 25
- 对象详情: User{name='张三', age=25}
1
2
3
4

核心API说明:

  • readValue(String content, Class<T> valueType):从 JSON 字符串读取并反序列化为指定 Class 的对象。
  • readValue(File src, Class<T> valueType):从文件读取 JSON 内容并反序列化。
  • readValue(URL src, Class<T> valueType):从 URL 读取 JSON 内容并反序列化。
  • readValue(InputStream src, Class<T> valueType):从输入流读取 JSON 内容并反序列化。
  • readValue(Reader src, Class<T> valueType):从 Reader 读取 JSON 内容并反序列化。
  • readValue(String content, TypeReference<T> valueTypeRef):用于反序列化泛型类型(如 List<User> 或 Map<String, Object>),将在后续章节介绍。

注意事项:

  • 目标 Java 类(如 User)通常需要一个无参构造函数,Jackson 在创建对象实例时会使用它。
  • 需要提供 Getter 和 Setter 方法,或者将字段声明为 public(不推荐),以便 Jackson 访问和设置属性值。
  • JSON 字符串中的 字段名 默认需要与 Java 类中的属性名完全匹配(区分大小写)。可以通过注解(如 @JsonProperty)来自定义映射关系。

# 四、树模型处理复杂 JSON(JsonNode 操作)

当 JSON 结构复杂、嵌套层次深,或者结构不固定、需要动态访问时,将 JSON 直接映射为预定义的 Java POJO 可能不太方便或不灵活。Jackson 提供了树模型(Tree Model),它将 JSON 数据解析为一个由 JsonNode 对象组成的树状结构,允许你像操作 DOM 树一样遍历和访问 JSON 的各个部分。

JsonNode 是一个抽象类,代表 JSON 结构中的一个节点,它可以是对象、数组、字符串、数字、布尔值或 null。

# 1. 基础 JsonNode 操作

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * 演示如何使用 Jackson 的 JsonNode (树模型) 解析和访问 JSON 数据
 */
public class JacksonJsonNodeExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 2. 准备一个包含嵌套结构的 JSON 字符串
        String jsonString = "{\"name\":\"赵六\",\"age\":22,\"active\":true,\"address\":{\"city\":\"上海\",\"zip\":\"200000\"},\"scores\":[95, 88, 76]}";

        try {
            // 3. 使用 readTree() 方法将 JSON 字符串解析为 JsonNode 树的根节点
            JsonNode rootNode = objectMapper.readTree(jsonString);

            // 4. 访问顶层基本类型的字段
            // 使用 get() 方法获取字段对应的 JsonNode,然后使用 asText(), asInt(), asBoolean() 等方法获取具体类型的值
            String name = rootNode.get("name").asText();
            int age = rootNode.get("age").asInt();
            boolean active = rootNode.get("active").asBoolean();

            System.out.println("基本信息:");
            System.out.println("- 姓名: " + name);
            System.out.println("- 年龄: " + age);
            System.out.println("- 是否活跃: " + active);

            // 5. 访问嵌套对象 (Object Node)
            // get() 方法返回的仍然是 JsonNode
            JsonNode addressNode = rootNode.get("address");
            // 检查 addressNode 是否真的是一个对象节点 (可选但推荐)
            if (addressNode != null && addressNode.isObject()) {
                String city = addressNode.get("city").asText();
                String zip = addressNode.get("zip").asText();
                System.out.println("地址信息:");
                System.out.println("- 城市: " + city);
                System.out.println("- 邮编: " + zip);
            } else {
                System.out.println("地址信息: 未找到或格式不正确");
            }

            // 6. 访问数组 (Array Node)
            JsonNode scoresNode = rootNode.get("scores");
            // 检查 scoresNode 是否是一个数组节点
            if (scoresNode != null && scoresNode.isArray()) {
                System.out.println("分数列表:");
                // 遍历数组节点
                for (JsonNode scoreNode : scoresNode) {
                    System.out.println("- " + scoreNode.asInt());
                }
                // 获取特定索引的元素
                if (scoresNode.size() > 0) {
                    System.out.println("- 第一个分数: " + scoresNode.get(0).asInt());
                }
            }

            // 7. 检查字段是否存在
            // 使用 has() 方法检查字段是否存在,避免 NullPointerException
            if (rootNode.has("email")) {
                System.out.println("- 邮箱: " + rootNode.get("email").asText());
            } else {
                System.out.println("- 邮箱: 未在 JSON 中提供");
            }
            
            // 8. 使用 path() 方法安全地访问字段 (推荐)
            // path() 方法类似于 get(),但如果字段不存在,它会返回一个 MissingNode 而不是 null,
            // 对 MissingNode 调用 asText() 等方法会返回默认值 (如 "", 0, false),更安全。
            String nonExistentField = rootNode.path("nonExistent").asText("默认值"); // 提供默认值
            System.out.println("- 不存在的字段 (path): " + rootNode.path("nonExistent").asText()); // 未提供默认值,返回空字符串
            System.out.println("- 不存在的字段 (path, with default): " + nonExistentField);

        } catch (JsonProcessingException e) {
            System.err.println("解析 JSON 到 JsonNode 时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

输出结果:

基本信息:
- 姓名: 赵六
- 年龄: 22
- 是否活跃: true
地址信息:
- 城市: 上海
- 邮编: 200000
分数列表:
- 95
- 88
- 76
- 第一个分数: 95
- 邮箱: 未在 JSON 中提供
- 不存在的字段 (path): 
- 不存在的字段 (path, with default): 默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

核心API说明:

  • readTree(String content) / readTree(InputStream in) / readTree(File file) / etc.:将 JSON 输入解析为一个 JsonNode 对象(树的根节点)。
  • JsonNode.get(String fieldName):获取对象节点中指定字段名的子节点。如果字段不存在,返回 null。
  • JsonNode.get(int index):获取数组节点中指定索引的元素节点。如果索引越界,返回 null。
  • JsonNode.path(String fieldName):安全地获取对象节点中的子节点。如果字段不存在,返回一个特殊的 MissingNode 对象,而不是 null。这有助于避免 NullPointerException。
  • JsonNode.path(int index):安全地获取数组节点中的元素节点。如果索引越界,返回 MissingNode。
  • JsonNode.asText() / asText(String defaultValue):将节点值转换为文本。如果节点不是文本类型或不存在(path 方法),asText() 返回空字符串 "",asText(defaultValue) 返回指定的默认值。
  • JsonNode.asInt() / asInt(int defaultValue):将节点值转换为整数。
  • JsonNode.asDouble() / asDouble(double defaultValue):将节点值转换为双精度浮点数。
  • JsonNode.asBoolean() / asBoolean(boolean defaultValue):将节点值转换为布尔值。
  • JsonNode.isObject():检查节点是否为 JSON 对象。
  • JsonNode.isArray():检查节点是否为 JSON 数组。
  • JsonNode.isValueNode():检查节点是否为值节点(字符串、数字、布尔、null)。
  • JsonNode.isNull():检查节点是否为 JSON null。
  • JsonNode.has(String fieldName) / has(int index):检查对象节点或数组节点是否包含指定的字段或索引。
  • JsonNode.size():获取数组节点中的元素数量,或对象节点中的字段数量。
  • JsonNode.elements():返回一个 Iterator<JsonNode>,用于遍历数组节点的所有元素。
  • JsonNode.fields():返回一个 Iterator<Map.Entry<String, JsonNode>>,用于遍历对象节点的所有字段(键值对)。

# 2. 处理复杂JSON数据结构

JsonNode 特别适合处理嵌套层次深、包含混合类型数组或结构可能变化的复杂 JSON。

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
// import com.fasterxml.jackson.databind.node.ArrayNode; // 如果需要创建或修改数组节点
// import com.fasterxml.jackson.databind.node.ObjectNode; // 如果需要创建或修改对象节点
import java.util.Iterator;
import java.util.Map;

/**
 * 使用 Jackson JsonNode 处理更复杂的 JSON 结构示例
 */
public class JacksonComplexJsonExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // 2. 准备一个结构复杂的 JSON 字符串
        String complexJsonString = "{"
            + "\"id\": \"user123\","
            + "\"personalInfo\": {"
                + "\"name\": \"李四\","
                + "\"age\": 30,"
                + "\"address\": {"
                    + "\"street\": \"人民路 100 号\","
                    + "\"city\": \"深圳\","
                    + "\"country\": \"中国\""
                + "}"
            + "},"
            + "\"contacts\": ["
                + "{\"type\": \"email\", \"value\": \"lisi@example.com\", \"primary\": true},"
                + "{\"type\": \"phone\", \"value\": \"13912345678\"}," // 注意: 这个联系方式没有 primary 字段
                + "{\"type\": \"wechat\", \"value\": \"lisi_wx\", \"primary\": false}"
            + "],"
            + "\"tags\": [\"developer\", \"java\", \"json\"],"
            + "\"metadata\": {"
                + "\"lastLogin\": \"2024-08-20T10:30:00Z\","
                + "\"sourceSystem\": \"SystemA\""
            + "}"
        + "}";

        try {
            // 3. 解析 JSON 字符串为 JsonNode 树
            JsonNode root = objectMapper.readTree(complexJsonString);

            // 4. 访问深层嵌套对象字段
            // 使用链式 path() 调用来安全访问,避免 NullPointerException
            String city = root.path("personalInfo").path("address").path("city").asText("未知城市");
            System.out.println("城市: " + city);

            // 5. 遍历复杂对象数组 (contacts)
            System.out.println("\n联系方式列表:");
            JsonNode contactsNode = root.path("contacts");
            if (contactsNode.isArray()) {
                for (JsonNode contact : contactsNode) {
                    String type = contact.path("type").asText();
                    String value = contact.path("value").asText();
                    // 处理可能缺失的布尔字段,提供默认值 false
                    boolean isPrimary = contact.path("primary").asBoolean(false); 
                    System.out.println("- 类型: " + type + ", 值: " + value + ", 主要联系方式: " + (isPrimary ? "是" : "否"));
                }
            }

            // 6. 遍历简单数组 (tags)
            System.out.println("\n标签列表:");
            JsonNode tagsNode = root.path("tags");
            Iterator<JsonNode> tagElements = tagsNode.elements(); // 使用 iterator
            while (tagElements.hasNext()) {
                System.out.println("- " + tagElements.next().asText());
            }

            // 7. 遍历对象字段 (metadata)
            System.out.println("\n元数据信息:");
            JsonNode metadataNode = root.path("metadata");
            Iterator<Map.Entry<String, JsonNode>> fields = metadataNode.fields(); // 使用 iterator
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                System.out.println("- " + field.getKey() + ": " + field.getValue().asText());
            }
            
            // 8. 检查特定字段的类型
            JsonNode ageNode = root.path("personalInfo").path("age");
            if (ageNode.isInt()) {
                System.out.println("\n年龄字段是整数类型。");
            }

            // 9. (高级) 修改 JsonNode (需要使用 ObjectNode/ArrayNode)
            // 注意: readTree 返回的是不可变 JsonNode,需要先转换为 ObjectNode/ArrayNode
            // if (root.isObject()) {
            //     ObjectNode mutableRoot = (ObjectNode) root;
            //     mutableRoot.put("newField", "newValue"); // 添加新字段
            //     mutableRoot.remove("tags"); // 删除字段
                 
            //     ObjectNode personalInfo = (ObjectNode) mutableRoot.path("personalInfo");
            //     personalInfo.put("age", 31); // 修改现有字段值
                 
            //     System.out.println("\n修改后的 JSON (部分):");
            //     System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mutableRoot));
            // }


        } catch (JsonProcessingException e) {
            System.err.println("处理复杂 JSON 时出错: " + e.getMessage());
            e.printStackTrace();
        // } catch (IOException e) { // 如果有写操作
        //     System.err.println("写入修改后的 JSON 时出错: " + e.getMessage());
        //     e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

输出结果 (不含修改部分):

城市: 深圳

联系方式列表:
- 类型: email, 值: lisi@example.com, 主要联系方式: 是
- 类型: phone, 值: 13912345678, 主要联系方式: 否
- 类型: wechat, 值: lisi_wx, 主要联系方式: 否

标签列表:
- developer
- java
- json

元数据信息:
- lastLogin: 2024-08-20T10:30:00Z
- sourceSystem: SystemA

年龄字段是整数类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

扩展API说明:

  • JsonNode.fieldNames(): 返回对象节点所有字段名的迭代器 Iterator<String>。
  • ObjectMapper.createObjectNode(): 创建一个空的 ObjectNode (可变的 JsonNode)。
  • ObjectMapper.createArrayNode(): 创建一个空的 ArrayNode (可变的 JsonNode)。
  • ObjectNode.put(String fieldName, ...): 在对象节点中添加或更新字段 (支持多种值类型)。
  • ObjectNode.set(String fieldName, JsonNode value): 设置字段的值为另一个 JsonNode。
  • ObjectNode.remove(String fieldName): 移除对象节点中的字段。
  • ArrayNode.add(...): 向数组节点末尾添加元素 (支持多种值类型)。
  • ArrayNode.set(int index, JsonNode value): 设置数组节点指定索引处的值。
  • ArrayNode.remove(int index): 移除数组节点指定索引处的元素。

树模型提供了极高的灵活性,尤其是在处理来自外部系统、结构可能变化的 JSON 数据时非常有用。

# 五、集合类型与 JSON 转换(List/Map 操作)

Jackson 能够轻松地在 JSON 数组与 Java List、JSON 对象与 Java Map 之间进行转换。这对于处理批量数据或未知结构的 JSON 非常方便。

# 1. JSON 数组 <-> List 对象

当 JSON 数据是一个数组,且数组中的每个元素都对应同一种 Java 对象时,可以将其反序列化为 List<YourObjectType>。

关键:TypeReference

由于 Java 的泛型类型擦除,objectMapper.readValue(jsonString, List.class) 无法得知 List 中元素的具体类型(它只会得到 List<Object> 或 List<LinkedHashMap>)。为了在运行时保留并告知 Jackson 泛型的具体类型(如 List<User>),我们需要使用 com.fasterxml.jackson.core.type.TypeReference。

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;
import java.util.ArrayList; // For creating a list to serialize

/**
 * 演示 Jackson 在 JSON 数组和 Java List<T> 之间的转换
 */
public class JacksonJsonToListExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // --- JSON Array to List<User> ---

        // 2. 准备一个 JSON 数组字符串
        String jsonArrayString = "["
            + "{\"name\":\"张三\",\"age\":25},"
            + "{\"name\":\"李四\",\"age\":30},"
            + "{\"name\":\"王五\",\"age\":28}"
        + "]";

        try {
            // 3. 使用 readValue() 和 TypeReference 反序列化 JSON 数组为 List<User>
            // TypeReference 是一个抽象类,通过匿名内部类的方式使用,{} 表示创建其子类实例
            List<User> userList = objectMapper.readValue(
                jsonArrayString,
                new TypeReference<List<User>>() {} // 关键点:指明泛型类型为 List<User>
            );

            // 4. 输出反序列化结果
            System.out.println("JSON 数组成功反序列化为 List<User>:");
            System.out.println("- 列表大小: " + userList.size());
            // 遍历 List 并打印每个 User 对象
            userList.forEach(user ->
                System.out.println("  - 用户: " + user.getName() + ", 年龄: " + user.getAge())
            );

        } catch (JsonProcessingException e) {
            System.err.println("反序列化 JSON 数组到 List 时出错: " + e.getMessage());
            e.printStackTrace();
        }

        // --- List<User> to JSON Array ---

        // 5. 创建一个 List<User> 实例
        List<User> usersToSerialize = new ArrayList<>();
        usersToSerialize.add(new User("赵六", 22));
        usersToSerialize.add(new User("孙七", 35));

        try {
            // 6. 使用 writeValueAsString() 将 List<User> 序列化为 JSON 数组字符串
            String listAsJson = objectMapper.writeValueAsString(usersToSerialize);

            // 7. 输出序列化结果
            System.out.println("\nList<User> 成功序列化为 JSON 数组:");
            System.out.println(listAsJson);

            // 8. (可选) 格式化输出
            String prettyListJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(usersToSerialize);
            System.out.println("\n格式化后的 JSON 数组:");
            System.out.println(prettyListJson);

        } catch (JsonProcessingException e) {
            System.err.println("序列化 List 到 JSON 数组时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
    // User 类定义同上
}

// User 类定义 (需要与前面的 User 类保持一致)
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; }
    @Override
    public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

输出结果:

JSON 数组成功反序列化为 List<User>:
- 列表大小: 3
  - 用户: 张三, 年龄: 25
  - 用户: 李四, 年龄: 30
  - 用户: 王五, 年龄: 28

List<User> 成功序列化为 JSON 数组:
[{"name":"赵六","age":22},{"name":"孙七","age":35}]

格式化后的 JSON 数组:
[ {
  "name" : "赵六",
  "age" : 22
}, {
  "name" : "孙七",
  "age" : 35
} ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2. JSON 对象 <-> Map

当 JSON 数据是一个对象,但其结构预先未知或字段不固定,或者你只是想将其作为通用键值对集合处理时,可以将其反序列化为 Map<String, Object>。

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Map;
import java.util.HashMap; // For creating a Map to serialize
import java.util.LinkedHashMap; // Jackson 默认反序列化对象为 LinkedHashMap

/**
 * 演示 Jackson 在 JSON 对象和 Java Map<String, Object> 之间的转换
 */
public class JacksonJsonToMapExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // --- JSON Object to Map<String, Object> ---

        // 2. 准备一个 JSON 对象字符串
        String jsonObjectString = "{"
            + "\"productName\": \"笔记本电脑\","
            + "\"quantity\": 10,"
            + "\"price\": 4999.99,"
            + "\"inStock\": true,"
            + "\"specs\": {\"cpu\":\"i5\",\"ram\":\"16GB\"}" // 嵌套对象会成为 Map 中的 Map
        + "}";

        try {
            // 3. 使用 readValue() 和 TypeReference 反序列化 JSON 对象为 Map<String, Object>
            Map<String, Object> productMap = objectMapper.readValue(
                jsonObjectString,
                new TypeReference<Map<String, Object>>() {} // 指明泛型类型
                // Jackson 默认会将 JSON 对象反序列化为 LinkedHashMap 来保持字段顺序
            );

            // 4. 输出反序列化结果
            System.out.println("JSON 对象成功反序列化为 Map<String, Object>:");
            System.out.println("- Map 大小: " + productMap.size());
            // 遍历 Map 并打印键、值及其值的 Java 类型
            productMap.forEach((key, value) ->
                System.out.println("  - Key: " + key + ", Value: " + value + " (Type: " + value.getClass().getSimpleName() + ")")
            );

            // 5. 访问 Map 中的特定值 (需要进行类型转换)
            String productName = (String) productMap.get("productName");
            // JSON 数字可能被解析为 Integer, Long, Double, BigDecimal 等,最好转为 Number 再取具体值
            int quantity = ((Number) productMap.get("quantity")).intValue();
            double price = ((Number) productMap.get("price")).doubleValue();
            boolean inStock = (Boolean) productMap.get("inStock");
            
            // 访问嵌套的 Map
            @SuppressWarnings("unchecked") // 抑制未检查的转换警告
            Map<String, String> specsMap = (Map<String, String>) productMap.get("specs");
            String cpu = specsMap.get("cpu");

            System.out.println("\n直接访问 Map 中的值:");
            System.out.println("- 产品名称: " + productName);
            System.out.println("- 数量: " + quantity);
            System.out.println("- 价格: " + price);
            System.out.println("- 是否有货: " + inStock);
            System.out.println("- CPU 规格: " + cpu);

        } catch (JsonProcessingException e) {
            System.err.println("反序列化 JSON 对象到 Map 时出错: " + e.getMessage());
            e.printStackTrace();
        } catch (ClassCastException cce) {
            System.err.println("访问 Map 值时类型转换错误: " + cce.getMessage());
        }

        // --- Map<String, Object> to JSON Object ---

        // 6. 创建一个 Map<String, Object> 实例
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("id", 101);
        dataMap.put("status", "processed");
        dataMap.put("timestamp", System.currentTimeMillis());
        Map<String, String> details = new HashMap<>();
        details.put("source", "api");
        details.put("correlationId", "corr-12345");
        dataMap.put("details", details); // 嵌套 Map

        try {
            // 7. 使用 writeValueAsString() 将 Map 序列化为 JSON 对象字符串
            String mapAsJson = objectMapper.writeValueAsString(dataMap);

            // 8. 输出序列化结果
            System.out.println("\nMap<String, Object> 成功序列化为 JSON 对象:");
            System.out.println(mapAsJson);
            
            // 9. (可选) 格式化输出
            String prettyMapJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(dataMap);
            System.out.println("\n格式化后的 JSON 对象:");
            System.out.println(prettyMapJson);

        } catch (JsonProcessingException e) {
            System.err.println("序列化 Map 到 JSON 对象时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

输出结果:

JSON 对象成功反序列化为 Map<String, Object>:
- Map 大小: 5
  - Key: productName, Value: 笔记本电脑 (Type: String)
  - Key: quantity, Value: 10 (Type: Integer)
  - Key: price, Value: 4999.99 (Type: Double)
  - Key: inStock, Value: true (Type: Boolean)
  - Key: specs, Value: {cpu=i5, ram=16GB} (Type: LinkedHashMap)

直接访问 Map 中的值:
- 产品名称: 笔记本电脑
- 数量: 10
- 价格: 4999.99
- 是否有货: true
- CPU 规格: i5

Map<String, Object> 成功序列化为 JSON 对象:
{"details":{"source":"api","correlationId":"corr-12345"},"status":"processed","timestamp":1692500000000,"id":101} // timestamp 会变化

格式化后的 JSON 对象:
{
  "details" : {
    "source" : "api",
    "correlationId" : "corr-12345"
  },
  "status" : "processed",
  "timestamp" : 1692500000000,
  "id" : 101
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Map 值类型映射规则:

  • JSON 字符串 → Java String
  • JSON 数字 → Java Integer, Long, Double, BigDecimal (取决于数字的大小和是否有小数)
  • JSON 布尔值 (true/false) → Java Boolean
  • JSON null → Java null
  • JSON 对象 → Java LinkedHashMap<String, Object> (默认,保持字段顺序)
  • JSON 数组 → Java ArrayList<Object>

使用 Map<String, Object> 提供了一种处理动态或未知结构 JSON 的灵活方式,但缺点是访问值时需要进行类型检查和转换。

# 六、JSON 格式化与自定义配置

ObjectMapper 提供了丰富的配置选项,允许你定制 Jackson 的序列化和反序列化行为,例如日期格式、是否忽略未知属性、是否缩进输出等。

这些配置可以通过 ObjectMapper 的 configure(), enable(), disable() 方法设置,或者直接操作 SerializationFeature 和 DeserializationFeature 枚举。

代码示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

/**
 * 演示如何配置 ObjectMapper 来自定义 Jackson 的序列化和反序列化行为
 */
public class JacksonCustomConfigExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例
        ObjectMapper objectMapper = new ObjectMapper();

        // --- 配置序列化特性 (SerializationFeature) ---

        // a) 开启 JSON 格式化(缩进)输出
        objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
        // 或者 objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        // b) 配置日期格式:将 Date 对象序列化为指定格式的字符串,而不是默认的时间戳
        // 关闭将日期写为时间戳的特性
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        // 设置自定义的日期格式
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); // ISO 8601 格式
        dateFormat.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); // 设置时区
        objectMapper.setDateFormat(dateFormat);

        // c) 配置序列化时忽略空的 Bean (如果一个对象所有属性都为 null)
        // objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // 默认为 true, 设为 false 则不抛异常

        // --- 配置反序列化特性 (DeserializationFeature) ---

        // d) 配置反序列化时忽略 JSON 中存在但 Java 对象中不存在的属性
        // 如果不配置,遇到未知属性时会抛出 UnrecognizedPropertyException 异常
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // e) 允许将 JSON 中的单个值(非数组)反序列化为只有一个元素的集合或数组
        // objectMapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);

        // --- 序列化示例 ---
        try {
            // 创建一个包含日期的示例对象
            UserWithDate user = new UserWithDate("王五", 28, new Date());
            // 序列化为配置后的 JSON 字符串
            String jsonString = objectMapper.writeValueAsString(user);
            System.out.println("经过自定义配置后的序列化 JSON:\n" + jsonString);
        } catch (JsonProcessingException e) {
            System.err.println("序列化时出错: " + e.getMessage());
            e.printStackTrace();
        }

        // --- 反序列化示例 ---
        try {
            // 准备一个包含未知属性 "extraField" 和指定日期格式的 JSON
            String jsonWithExtra = "{"
                + "\"name\": \"李四\","
                + "\"age\": 30,"
                + "\"createTime\": \"2023-10-01T14:30:00.000+08:00\"," // 匹配配置的日期格式
                + "\"extraField\": \"这个字段在 UserWithDate 类中不存在\"" 
            + "}";

            // 反序列化 JSON
            UserWithDate parsedUser = objectMapper.readValue(jsonWithExtra, UserWithDate.class);
            System.out.println("\n成功反序列化 (忽略了未知属性 extraField):");
            System.out.println("- 用户名: " + parsedUser.getName());
            System.out.println("- 年龄: " + parsedUser.getAge());
            // 使用配置的 SimpleDateFormat 来格式化 Date 对象进行验证
            System.out.println("- 创建时间: " + dateFormat.format(parsedUser.getCreateTime()));

        } catch (JsonProcessingException e) {
            System.err.println("\n反序列化时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

/**
 * 包含日期字段的用户类,用于演示配置效果
 */
class UserWithDate {
    private String name;
    private int age;
    private Date createTime; // 日期字段

    // 无参构造函数
    public UserWithDate() {}

    // 有参构造函数
    public UserWithDate(String name, int age, Date createTime) {
        this.name = name;
        this.age = age;
        this.createTime = createTime;
    }

    // Getters and Setters
    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 Date getCreateTime() { return createTime; }
    public void setCreateTime(Date createTime) { this.createTime = createTime; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107

输出结果 (日期会根据当前时间变化):

经过自定义配置后的序列化 JSON:
{
  "name" : "王五",
  "age" : 28,
  "createTime" : "2024-08-20T17:45:00.123+08:00" 
}

成功反序列化 (忽略了未知属性 extraField):
- 用户名: 李四
- 年龄: 30
- 创建时间: 2023-10-01T14:30:00.000+08:00
1
2
3
4
5
6
7
8
9
10
11

常用配置选项 (Features):

  1. SerializationFeature (控制序列化行为):

    • INDENT_OUTPUT: 格式化 (美化) 输出 JSON 字符串。
    • WRITE_DATES_AS_TIMESTAMPS: (默认 true) 将 java.util.Date 序列化为数字时间戳。设为 false 并配置 setDateFormat() 可自定义格式。
    • WRITE_DATE_KEYS_AS_TIMESTAMPS: 控制 Map 的 Date 类型 key 如何序列化。
    • WRITE_ENUMS_USING_TO_STRING: (默认 false) 使用枚举的 toString() 方法作为其 JSON 值,而不是默认的 name()。
    • FAIL_ON_EMPTY_BEANS: (默认 true) 当尝试序列化没有属性的 "空" Java 对象时是否抛出异常。
    • WRAP_ROOT_VALUE: 如果类上有 @JsonRootName 注解,是否将 JSON 包裹在以此命名的根元素中。
  2. DeserializationFeature (控制反序列化行为):

    • FAIL_ON_UNKNOWN_PROPERTIES: (默认 true) 当 JSON 中包含 Java 对象没有的属性时,是否抛出异常。设为 false 可忽略未知属性。
    • FAIL_ON_NULL_FOR_PRIMITIVES: (默认 false) 当 JSON null 值尝试赋给 Java 基本类型 (int, boolean 等) 时是否失败。
    • FAIL_ON_NUMBERS_FOR_ENUMS: (默认 false) 是否允许使用数字来反序列化枚举 (基于索引)。
    • ACCEPT_EMPTY_STRING_AS_NULL_OBJECT: 是否将空字符串 "" 反序列化为 null 对象。
    • ACCEPT_SINGLE_VALUE_AS_ARRAY: 是否允许将 JSON 中的单个值自动包装成单元素数组/集合。
    • UNWRAP_ROOT_VALUE: 如果期望的 JSON 根对象被包裹 (如使用了 @JsonRootName), 是否先解开这一层。
  3. 日期和时间格式化:

    • setDateFormat(DateFormat df): 设置用于序列化和反序列化 java.util.Date 的格式。

    • 推荐使用 jackson-datatype-jsr310 模块 来处理 Java 8 的 java.time API (如 LocalDate, LocalDateTime),它提供了更现代和健壮的日期时间处理。添加依赖后,ObjectMapper 会自动注册该模块:

      <dependency>
          <groupId>com.fasterxml.jackson.datatype</groupId>
          <artifactId>jackson-datatype-jsr310</artifactId>
      </dependency>
      
      1
      2
      3
      4

      然后可以使用 @JsonFormat 注解更方便地控制 java.time 对象的格式。

通过组合这些配置,可以使 Jackson 的行为完全符合你的应用需求。

# 七、常见 Jackson 注解使用

除了全局配置 ObjectMapper,Jackson 还提供了丰富的注解,可以直接用在 Java 类或属性上,以更细粒度地控制特定字段或类的序列化/反序列化行为。

代码示例:

import com.fasterxml.jackson.annotation.*; // 引入 Jackson 注解包
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.util.Date;
import java.util.TimeZone;

/**
 * 演示常用 Jackson 注解的使用
 */
public class JacksonAnnotationExample {

    public static void main(String[] args) {
        // 1. 创建 ObjectMapper 实例,并开启格式化输出以便观察
        ObjectMapper objectMapper = new ObjectMapper()
            .enable(SerializationFeature.INDENT_OUTPUT)
            // 配置默认时区,影响未使用 timezone 的 @JsonFormat
            .setTimeZone(TimeZone.getTimeZone("UTC")); 

        // --- 序列化带注解的对象 ---
        try {
            // 创建一个带注解的用户对象实例
            AnnotatedUser user = new AnnotatedUser();
            user.setId("user001");
            user.setFullName("张三丰"); // 将被序列化为 "user_full_name"
            user.setAge(100);
            user.setPassword("sensitive_data_123"); // 将被 @JsonIgnore 忽略,不出现在 JSON 中
            user.setEmail(null); // 将被 @JsonInclude(Include.NON_NULL) 忽略
            user.setRegistrationDate(new Date()); // 将根据 @JsonFormat 格式化
            user.setLastLoginTime(new Date()); // 将使用默认配置或全局配置的格式

            // 序列化对象
            String json = objectMapper.writeValueAsString(user);
            System.out.println("使用注解序列化后的 JSON 输出:\n" + json);

        } catch (JsonProcessingException e) {
            System.err.println("序列化带注解的对象时出错: " + e.getMessage());
            e.printStackTrace();
        }

        // --- 反序列化到带注解的对象 ---
        try {
            // 准备一个包含自定义字段名和日期格式的 JSON
            String inputJson = "{"
                + "\"id\": \"user002\","            // 正常字段
                + "\"user_full_name\": \"李四\", " // 匹配 @JsonProperty("user_full_name")
                + "\"age\": 35,"
                // + "\"password\": \"ignored_anyway\"," // 即使提供了,也会因 @JsonIgnore 而忽略
                + "\"email\": \"lisi@example.com\","  // 正常字段
                + "\"registered_at\": \"2023-11-15 09:30:00\", " // 匹配 @JsonFormat
                + "\"last_login\": " + System.currentTimeMillis() + "," // 时间戳,需要 ObjectMapper 配置支持
                + "\"extra_info\": \"This field is ignored by @JsonIgnoreProperties\"" // 未知字段
            + "}";
            
            // 配置 ObjectMapper 支持时间戳反序列化 (如果需要)
            objectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); // 确保读取毫秒时间戳
            objectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 确保可以读取时间戳

            // 反序列化 JSON
            AnnotatedUser parsedUser = objectMapper.readValue(inputJson, AnnotatedUser.class);
            System.out.println("\n从 JSON 反序列化后的 AnnotatedUser 对象:");
            System.out.println("- ID: " + parsedUser.getId());
            System.out.println("- 全名: " + parsedUser.getFullName()); // 注意是 Java 属性名
            System.out.println("- 年龄: " + parsedUser.getAge());
            System.out.println("- 密码: " + parsedUser.getPassword()); // 即使 JSON 中有,反序列化后也为 null 或默认值
            System.out.println("- 邮箱: " + parsedUser.getEmail());
            System.out.println("- 注册日期: " + parsedUser.getRegistrationDate()); // Date 对象
            System.out.println("- 最后登录: " + parsedUser.getLastLoginTime()); // Date 对象

        } catch (JsonProcessingException e) {
            System.err.println("\n反序列化到带注解的对象时出错: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

/**
 * 使用了多种 Jackson 注解的用户类
 */
// 类级别注解
@JsonInclude(JsonInclude.Include.NON_NULL) // 序列化时,值为 null 的字段将被忽略
@JsonIgnoreProperties(ignoreUnknown = true) // 反序列化时,忽略 JSON 中存在但此类没有对应字段的属性
// @JsonPropertyOrder({"id", "fullName", "age", "email", "registrationDate", "lastLoginTime"}) // (可选) 指定序列化时的字段顺序
class AnnotatedUser {

    // 字段级别注解

    @JsonProperty("userId") // 在 JSON 中使用 "userId" 作为字段名,而不是 "id"
    private String id;

    @JsonProperty("user_full_name") // 在 JSON 中使用 "user_full_name"
    private String fullName;

    private int age; // 没有注解,使用默认字段名 "age"

    @JsonIgnore // 序列化和反序列化时完全忽略此字段
    private String password;

    // 默认情况下,null 值会被 @JsonInclude(Include.NON_NULL) 忽略
    private String email;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
    @JsonProperty("registered_at") // 自定义 JSON 字段名
    private Date registrationDate; // 使用指定格式和时区进行序列化/反序列化

    @JsonProperty("last_login")
    // 没有 @JsonFormat,将使用 ObjectMapper 的全局日期配置 (本例中是时间戳)
    private Date lastLoginTime;
    
    // Jackson 需要无参构造函数进行反序列化
    public AnnotatedUser() {}

    // Getters and Setters (Jackson 需要它们来访问私有字段)
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getFullName() { return fullName; }
    public void setFullName(String fullName) { this.fullName = fullName; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public Date getRegistrationDate() { return registrationDate; }
    public void setRegistrationDate(Date registrationDate) { this.registrationDate = registrationDate; }
    public Date getLastLoginTime() { return lastLoginTime; }
    public void setLastLoginTime(Date lastLoginTime) { this.lastLoginTime = lastLoginTime; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

输出结果 (日期和时间戳会变化):

使用注解序列化后的 JSON 输出:
{
  "userId" : "user001",
  "user_full_name" : "张三丰",
  "age" : 100,
  "registered_at" : "2024-08-20 17:45:00", 
  "last_login" : 1692524700123 
}

从 JSON 反序列化后的 AnnotatedUser 对象:
- ID: user002
- 全名: 李四
- 年龄: 35
- 密码: null
- 邮箱: lisi@example.com
- 注册日期: Tue Nov 15 09:30:00 CST 2023 
- 最后登录: Tue Aug 20 17:45:00 CST 2024 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

常用 Jackson 注解详解:

  1. 属性命名与包含控制:

    • @JsonProperty("jsonFieldName"): 指定 Java 属性在 JSON 中对应的字段名称。对于反序列化,它告诉 Jackson JSON 中的哪个字段映射到这个 Java 属性;对于序列化,它指定生成的 JSON 字段名。
    • @JsonIgnore: 完全忽略被注解的属性,序列化时不会输出,反序列化时不会读取。
    • @JsonInclude(JsonInclude.Include value): 控制属性在什么条件下才被序列化包含。常用值:
      • Include.ALWAYS: 总是包含(默认行为)。
      • Include.NON_NULL: 仅当属性值不为 null 时包含。
      • Include.NON_ABSENT: 仅当属性值不是 null 且对于 AtomicReference 不是 absent 时包含 (需要 jackson-datatype-jdk8 模块)。
      • Include.NON_EMPTY: 仅当属性值不为 null 且不为空(对 String, Collection, Map, Array 判断 isEmpty()/length == 0)时包含。
      • Include.NON_DEFAULT: 仅当属性值不等于其 Java 类型的默认值(如 int 的 0, boolean 的 false, 对象的 null)时包含。
    • @JsonPropertyOrder({"prop1", "prop2", ...}): (类级别注解) 指定序列化时属性的输出顺序。默认顺序可能不稳定。
  2. 格式化:

    • @JsonFormat(...): 用于格式化特定类型(主要是日期/时间)的序列化和反序列化。
      • shape: 指定输出形状,如 Shape.STRING (格式化字符串), Shape.NUMBER (时间戳)。
      • pattern: 当 shape=Shape.STRING 时,指定日期/时间格式 (如 "yyyy-MM-dd HH:mm:ss")。
      • timezone: 指定时区 (如 "GMT+8", "Asia/Shanghai")。
  3. 类级别控制:

    • @JsonIgnoreProperties({"prop1", "prop2"}): (类级别注解) 忽略类中指定的几个属性。
    • @JsonIgnoreProperties(ignoreUnknown = true): (类级别注解) 在反序列化时,如果 JSON 中有多余的、类中不存在的字段,则忽略它们而不抛出异常。等同于配置 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES = false,但更具针对性。
    • @JsonRootName("rootName"): (类级别注解) 配合 ObjectMapper 开启 SerializationFeature.WRAP_ROOT_VALUE 和 DeserializationFeature.UNWRAP_ROOT_VALUE 使用,可以在序列化时将整个 JSON 包裹在一个根键下,反序列化时则要求 JSON 必须有这个根键。
  4. 反序列化特定注解:

    • @JsonCreator: 标记一个构造函数或静态工厂方法作为创建对象实例的方式。当有多个构造函数或需要通过特定逻辑创建对象时使用。
    • @JsonProperty: 用在构造函数或工厂方法的参数上,指明 JSON 中的哪个字段对应这个参数。
    • @JsonAnySetter: 标记一个方法(通常接收 String key, Object value 两个参数),用于处理 JSON 中所有未明确映射到 Java 属性的字段。可以将这些“多余”的字段存入一个 Map 中。
    • @JsonAlias({"alias1", "alias2"}): 为一个属性提供别名。反序列化时,JSON 中使用原始属性名或任何一个别名都可以映射到该 Java 属性。
  5. 序列化特定注解:

    • @JsonAnyGetter: 标记一个返回 Map 的方法。该 Map 中的键值对会被序列化为 JSON 的顶层字段。
    • @JsonValue: 标记一个方法(通常是无参 getter),其返回值将代表整个对象被序列化。例如,枚举类可以用 @JsonValue 标记一个 getCode() 方法,使其序列化为 code 值而不是枚举名。

通过灵活运用这些注解,可以极大地简化和定制 Java 对象与 JSON 之间的映射逻辑。

总结

Jackson 作为 Spring Boot 的默认 JSON 库,凭借其全面的功能、优秀的性能和高度的灵活性,成为了 Java 世界中处理 JSON 的事实标准之一。

核心优势与功能回顾:

  1. 对象 <-> JSON 互转: 使用 ObjectMapper 可以轻松实现 Java POJO 与 JSON 字符串之间的双向绑定。
  2. 树模型 (JsonNode): 提供了灵活的方式来处理结构复杂、动态或未知的 JSON 数据,支持路径访问、类型检查和节点遍历。
  3. 集合支持: 通过 TypeReference 可以方便地处理 JSON 数组到 List、JSON 对象到 Map 的转换,支持泛型。
  4. 强大的配置: SerializationFeature 和 DeserializationFeature 提供了丰富的选项来全局定制序列化和反序列化行为(如日期格式、错误处理)。
  5. 丰富的注解: 提供了 @JsonProperty, @JsonIgnore, @JsonFormat, @JsonInclude 等大量注解,可以对特定类或属性进行精细控制。
  6. 生态系统: 拥有 jackson-datatype-jsr310 (Java 8 日期时间), jackson-dataformat-xml (XML支持) 等众多模块,扩展能力强。
  7. 性能: Jackson 通常被认为是性能最好的 Java JSON 库之一。

无论是构建 RESTful Web 服务、处理配置文件,还是进行各种数据交换,Jackson 都能提供强大而高效的解决方案。熟练掌握 Jackson 的使用,对于 Java 开发者来说是一项非常重要的技能。

编辑此页 (opens new window)
上次更新: 2025/04/06, 10:15:45
FastJson2(JSON处理库)

FastJson2(JSON处理库)→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式