CodeGym /课程 /JAVA 25 SELF /Jackson — 读取与写入 JSON,注解

Jackson — 读取与写入 JSON,注解

JAVA 25 SELF
第 46 级 , 课程 1
可用

1. Jackson 简介

在 Java 应用与外部世界之间进行数据交换,常可概括为这样一个问题:“如何把 Java 对象变成 JSON——再变回来?”当然,你可以用 String.split 和正则自己写一个解析器(甚至从折磨中获得快感),但在真实项目中没人这么做。

在 Java 中有几种常用的 JSON 库。其中最重要的是 Jackson。它如此流行,以至于已被纳入 Spring Boot,并被大量框架与库使用。

什么是 Jackson?

Jackson 是一个用于序列化(把 Java 对象转换为 JSON)和反序列化(相反操作)的强大而灵活的库。它由多个模块组成,但对 90% 的场景你只需两个:

  • jackson-core — 内核,低层解析器。
  • jackson-databind — 高层模块,负责把 Java 对象与 JSON 相互转换。

入门时只需记住一点:如果你看到类 ObjectMapper——那就是 Jackson。

之所以说 Jackson 是 Java 领域处理 JSON 的事实标准,是因为它兼具简洁与强大。为了序列化或反序列化数据,往往只需寥寥数行代码,对新手也很友好。同时它并不止于基础功能:借助注解与丰富的配置,你可以精细控制数据与对象之间的映射方式,无论是集合、嵌套实体,还是不同格式的日期。

同样重要的是,Jackson 运行速度快且内存占用低,这对需要处理大量数据的真实项目至关重要。库的维护者持续跟进新的 Java 版本以及 JSON 标准的最新变化——无论是简单应用还是大型企业系统,Jackson 都是可靠之选。

2. 读取 JSON(反序列化)

我们来试着读取一段 JSON 字符串并把它转换为 Java 对象。为此需要:

  • 一个数据类(例如,Person
  • 来自 Jackson 的 ObjectMapper

引入 Jackson

如果使用 Maven,请在 pom.xml 中添加:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

如果是 Gradle — 类似地:

implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'

示例类

public class Person {
    public String name;
    public int age;
}

说明: 为了简单起见,这里将字段设为 public。稍后我们会讨论私有字段与 getter/setter 的处理。

JSON 示例

{
  "name": "Alice",
  "age": 30
}

反序列化:把 JSON 变为对象

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"name\": \"Alice\", \"age\": 30}";

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

        System.out.println(person.name); // Alice
        System.out.println(person.age);  // 30
    }
}

这里 Jackson 会解析 JSON 字符串,查找与类的字段或其 getter 同名的键,并通过 readValue 将相应的值填充到对象中。

反序列化对象列表

假设我们有一个数组:

[
  { "name": "Bob", "age": 22 },
  { "name": "Eve", "age": 27 }
]

将其反序列化为列表:

import com.fasterxml.jackson.core.type.TypeReference;
// ...

String json = "[{\"name\": \"Bob\", \"age\": 22}, {\"name\": \"Eve\", \"age\": 27}]";
ObjectMapper mapper = new ObjectMapper();

List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});

for (Person p : people) {
    System.out.println(p.name + " (" + p.age + ")");
}

也许你会问:为什么不能直接写 mapper.readValue(json, List.class)? 还记得我们刚刚提到,Java 的泛型在编译期会被类型擦除吗?因此我们需要 TypeReference,让 Jackson 明确列表里应该是 Person 类型的对象。

3. 写出 JSON(序列化)

现在做相反的操作:把 Java 对象转为 JSON 字符串。

示例

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

String json = mapper.writeValueAsString(person);
System.out.println(json);
// {"name":"Charlie","age":40}

序列化对象列表

ObjectMapper mapper = new ObjectMapper();

List<Person> people = new ArrayList<>();
people.add(new Person("Anna", 25));
people.add(new Person("Dmitry", 31));

String json = mapper.writeValueAsString(people);
System.out.println(json);
// [{"name":"Anna","age":25},{"name":"Dmitry","age":31}]

写入文件

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

mapper.writeValue(new File("person.json"), person);
// 文件 person.json 现在包含 JSON 对象

美观(pretty)JSON

默认情况下,Jackson 会把所有内容写成一行。不过也可以使用更“面向人类”的输出——pretty printing。这意味着 JSON 字符串会以易读的形式输出:包含缩进、换行和整齐的格式。

与为追求紧凑而常常写成单行的“普通”JSON 不同,“美观”版本是给人看的——便于在日志、文件或屏幕上浏览数据结构。

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

String prettyJson = mapper.writerWithDefaultPrettyPrinter()
                          .writeValueAsString(person);

System.out.println(prettyJson);
/*
{
  "name" : "Charlie",
  "age" : 40
}
*/

4. Jackson 注解

Jackson 提供了大量注解,用来控制序列化与反序列化过程。以下是最常用的几种:

@JsonProperty

当 JSON 中的字段名与类中的字段名不同,可用它指定 JSON 中的名称。

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {
    @JsonProperty("full_name")
    public String name;

    public int age;
}
{"full_name": "Olga", "age": 28}

Jackson 会把 JSON 中的 full_name 映射到对象的 name 字段。

@JsonIgnore

若不希望序列化或反序列化某个字段:

import com.fasterxml.jackson.annotation.JsonIgnore;

public class Person {
    public String name;

    @JsonIgnore
    public int age;
}

即使对象中存在,该字段 age 也不会出现在 JSON 中。

@JsonInclude

控制哪些字段会进入 JSON。例如仅序列化非空字段:

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
    public String name;
    public Integer age;
}

age == null,JSON 中将没有键 "age"

@JsonFormat

用于为日期时间指定序列化格式。

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;

public class Event {
    public String title;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    public Date date;
}
Event event = new Event();
event.title = "Hackathon";
event.date = new Date();

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(event);
// {"title":"Hackathon","date":"2024-06-07 15:23:00"}

示例:合在一起

import com.fasterxml.jackson.annotation.*;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
    @JsonProperty("full_name")
    private String name;

    private int age;

    @JsonIgnore
    private String password;

    // 对于私有字段,必须提供 getter 和 setter!
    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; }
}

5. 实战:结合注解进行序列化与反序列化

让我们扩展一下你的练习应用——现在有一个 User 类,包含私有字段、注册日期,以及不应进入 JSON 的密码字段。

import com.fasterxml.jackson.annotation.*;
import java.util.Date;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    @JsonProperty("login")
    private String username;

    private int age;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private Date registered;

    @JsonIgnore
    private String password;

    // 必须提供 getter 和 setter!
    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; }

    public Date getRegistered() { return registered; }
    public void setRegistered(Date registered) { this.registered = registered; }

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

序列化

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

ObjectMapper mapper = new ObjectMapper();

User user = new User();
user.setUsername("superuser");
user.setAge(42);
user.setRegistered(new SimpleDateFormat("yyyy-MM-dd").parse("2024-06-07"));
user.setPassword("password123"); // 不会出现在 JSON!

String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);

System.out.println(json);
/*
{
  "login" : "superuser",
  "age" : 42,
  "registered" : "2024-06-07"
}
*/

反序列化

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

String json = "{ \"login\": \"superuser\", \"age\": 42, \"registered\": \"2024-06-07\" }";
User user = mapper.readValue(json, User.class);

System.out.println(user.getUsername()); // superuser
System.out.println(user.getAge());      // 42
System.out.println(user.getRegistered());// Fri Jun 07 00:00:00 ...
System.out.println(user.getPassword()); // null(这正是我们想要的!)

6. 使用 Jackson 时的常见错误

错误 #1:缺少无参构造器。
如果类没有无参构造器,Jackson 将无法创建该类的实例。这常见于只显式声明了带参数构造器的类。

错误 №2:私有字段没有 getter/setter。
如果把字段都设为私有却未提供 getter 和 setter,Jackson 在反序列化时(默认情况下)无法为其赋值。

错误 №3:字段名不匹配。
当 JSON 中的字段名与 Java 中的字段/getter 名称不同,Jackson 找不到对应关系。请使用 @JsonProperty

错误 №4:日期格式不匹配。
如果 JSON 中的日期格式与 Java 端期望的不一致,Jackson 会抛出解析错误。请使用 @JsonFormat 来配置。

错误 №5:试图序列化带有注解 @JsonIgnore 的字段。
这类字段不会进入 JSON——这不是 bug,而是特性。

错误 №6:在不指定元素类型的情况下序列化/反序列化集合。
如果不使用 TypeReference,Jackson 无法知道集合中元素的类型。

错误 №7:读/写文件时抛出异常。
处理文件时别忘了捕获并处理 IOException

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION