一、JSON 简述
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,是基于 JavaScript 的一个子集,但独立于语言,被广泛应用于前后端通信、配置文件、API 接口等领域。
JSON 在“传输 / 存储”层面是字符串,但在“语义”层面,它代表的是一种结构化数据格式。简单来说,传输时是字符串,使用时是数据结构。
这里是 JSON 的官方文档:传送门
1、核心特点
- 简洁易读:纯文本格式,人类和机器都容易读写。
- 语言无关:虽然源于 JavaScript,但几乎所有编程语言都支持解析和生成 JSON。
- 轻量级:相比 XML,JSON 结构更简单,冗余更少。
- 键值对结构:以键值对(Key-Value)存储数据,支持嵌套。
2、基本语法规则
六种数据类型,示例如下:
{
"name": "张三",
"age": 25,
"isStudent": false,
"skills": ["Python", "JavaScript"],
"address": {
"city": "北京",
"zipcode": "100000"
},
"married": null
}
3、注意事项
- 键名必须用双引号(单引号无效)。
- JSON 标准(RFC 8259)本身不允许注释。
- 无日期类型:日期需转为字符串(如 "2025-07-29")。
4、第三方 Java 库
| 库名 | 社区 | 典型场景 & 优势 | 最小示例 |
|---|---|---|---|
| Jackson | FasterXML | 企业级首选;与 Spring 无缝集成;支持流式、树模型、数据绑定;性能高、功能全。 | ObjectMapper mapper = new ObjectMapper();String json = mapper.writeValueAsString(obj); |
| Fastjson | Alibaba | 速度极快;支持 JSONPath、自动类型转换;国内大量使用。 | String json = JSON.toJSONString(obj); |
| Gson | API 极简;适合快速开发、Android;注解丰富;对泛型、嵌套对象支持好。 | Gson gson = new Gson();String json = gson.toJson(obj); |
二、什么是序列化?
序列化:把内存对象转换为字节序列(byte[]、文件、网络流)的过程。
反序列化:把字节序列还原为内存对象的过程。
序列化 / 反序列化的本质是对象和字节流的双向转换,让“活的”对象可以“死”着传输或存储。更广泛的来说,不限于字节流,任意可还原的中间表示也算序列化,如 JSON 文本。
三、Jackson 库
1、简介
序列化的方法有很多种,JDK 自带 Java 原生序列化(java.io.Serializable),零依赖,简单场景够用了。但我最常用的还是 Jackson 库。
Jackson 库是 Java 生态里的“JSON 事实标准”库,Spring Boot、Spring MVC 等框架都把它当作默认 JSON 引擎。因此,Spring Boot Starter Web 已间接包含 Jackson 库,无需额外引入依赖。
2、常用配置
实例 ObjectMapper 的可选配置项有很多种:
| 配置项 | 描述说明 | 配置枚举 |
|---|---|---|
| 日期不要时间戳 | 让 Date / LocalDateTime 变成可读字符串,而不是 long 毫秒值 | SerializationFeature.WRITE_DATES_AS_TIMESTAMPS |
| JSON 美化输出 | 生成带缩进、换行的易读格式 | SerializationFeature.INDENT_OUTPUT |
| 空 Bean 不抛异常 | 当类没有任何可见字段 / Getter 时,不再抛 InvalidDefinitionException,而是输出 {} | SerializationFeature.FAIL_ON_EMPTY_BEANS |
| 忽略未知属性 | 当 JSON 里出现 Bean 没有的字段时,静默忽略,不抛 UnrecognizedPropertyException | DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES |
| 允许注释 | 支持 // 或 /* */ 注释,解析器不再报错 | JsonParser.Feature.ALLOW_COMMENTS |
| 允许单引号 | 允许用单引号包裹字符串,兼容非标准 JSON | JsonParser.Feature.ALLOW_SINGLE_QUOTES |
| 允许尾部逗号 | 允许最后一个元素后面多余逗号,兼容 JSON5 习惯 | JsonParser.Feature.ALLOW_TRAILING_COMMA |
忽略 null 值 | 过滤掉值为 null 的字段 | Include.NON_NULL |
| 自定义日期格式 | 让日期按指定格式(如 yyyy-MM-dd HH:mm:ss)输出 / 解析 | 注册 JavaTimeModule |
常用配置的示例如下:
import com.fasterxml.jackson.databind.ObjectMapper;
ObjectMapper mapper = new ObjectMapper()
// JSON 美化输出
.enable(SerializationFeature.INDENT_OUTPUT)
// 忽略 null 值
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
3、常用序列化方法
Jackson 的 API 命名规律:
- value:要被序列化的 Java 对象,与 JSON 字面量相对应。
- write:把 Java 对象(value)往外写。
- read:把外部数据(JSON 字面量)读进来变成 Java 对象。
- tree:指一颗 JsonNode 对象树,一种可随机访问的内存树结构。ObjectNode 是 JsonNode 的可写子类,JsonNode 只读,ObjectNode 可改。
| 方向 | 方法 | 示例 |
|---|---|---|
| 对象 → JSON 字符串 | writeValueAsString(Object) | String json = mapper.writeValueAsString(user); |
| 对象 → 文件 / 网络流 | writeValue(File/OutputStream, Object) | mapper.writeValue(new File("user.json"), user); |
| JSON → 对象 | readValue(String/File/InputStream, Class) | User u = mapper.readValue(json, User.class); |
| JSON → 泛型集合 | readValue(..., TypeReference) | List<User> list = mapper.readValue(json, new TypeReference<List<User>>(){}); |
| JSON 字符串 → JsonNode | readTree(String) | JsonNode root = mapper.readTree(json); |
| 对象 → JsonNode | valueToTree(Object) | JsonNode node = mapper.valueToTree(user); |
| JsonNode → 对象 | treeToValue(JsonNode, Class) | User u = mapper.treeToValue(node, User.class); |
| 全局配置 | configure(..., boolean) / setSerializationInclusion(...) | 见上文 |
4、内存类型的互转
convertValue:内存类型之间的相互转换,当需要 POJO ↔ Map / List ↔ 数组等内存互转,又不想走 JSON 字符串时,用它就对了。
// POJO → Map
User user = new User("Tom", 18);
Map<String, Object> map = mapper.convertValue(user, new TypeReference<>() {});
// map = {name=Tom, age=18}
// Map → POJO
User user2 = mapper.convertValue(map, User.class);
// List → 数组
List<String> list = List.of("a", "b", "c");
String[] array = mapper.convertValue(list, String[].class);
// array = ["a","b","c"]
四、碎碎念
- 其实这些经常会被统一写到静态工具类里,兼顾统一配置、减少开销、语义收敛等问题,比如
JsonSchemaUtils.java。 - 这一篇本来是想讲如何使用 Jackson 的,结果来来回回研究序列化,越看越复杂,拖了好几天。上周刚开始时,我本来是想一天一更的,结果发现这个频率完全不现实。希望还是以自己满意的质量为主吧,坚持写下去。