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。
GO TO FULL VERSION