一、JSON 介绍
JSON(JavaScript Object Notation) 是一种非常常用的数据交换格式,用于在客户端和服务器之间传递数据。它具有轻量级、易于阅读和解析的特点,非常适合在 Web 应用和 RESTful API 中使用。
1. JSON 的基本结构
JSON 是一种键值对格式的数据表示方法,支持以下几种数据类型:
- 对象:用
{}包裹,表示一组键值对。
{
"name": "Alice",
"age": 25,
"isStudent": false
}
- 数组:用
[]包裹,表示一组有序的值。
[
"apple",
"banana",
"cherry"
]
- 基本数据类型:字符串、数字、布尔值、
null。
"string"
123
true
null
实例:
{
"name": "小明",
"age": 20,
"hobbies": ["读书", "跑步", "编程"],
"address": {
"city": "北京",
"street": "朝阳区"
},
"friends": [
{
"name": "小红",
"age": 22
},
{
"name": "小李",
"age": 23
}
],
"school": {
"name": "光明中学",
"location": {
"city": "北京",
"district": "海淀区"
}
}
}
总结:
- 对象(
{}):表示一个集合,包含多个键值对。 - 数组(
[]):表示一个列表,包含多个元素(可以是字符串、数字、对象等)。 - 嵌套结构:对象中可以包含对象或数组,数组中也可以包含对象,这种结构可以用来表示复杂的数据关系。
2. JSON 在 Web 开发中的应用场景
场景 1:客户端向服务器发送数据
当客户端(如浏览器或移动应用)需要向服务器发送数据时,通常会将数据封装为 JSON 格式,然后通过 HTTP 请求(如 POST 或 PUT)发送给服务器。
示例:用户注册
客户端需要向服务器发送用户信息,包括用户名、密码和邮箱。
JSON 数据示例:
{
"username": "Alice",
"password": "password123",
"email": "alice@example.com"
}
HTTP 请求示例:
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
{
"username": "Alice",
"password": "password123",
"email": "alice@example.com"
}
场景 2:服务器向客户端返回数据
服务器处理完客户端的请求后,通常会将结果封装为 JSON 格式返回给客户端。
示例:获取用户信息
客户端请求获取用户信息,服务器返回用户的基本信息。
JSON 数据示例:
{
"id": 1,
"username": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-01T12:00:00Z"
}
HTTP 响应示例:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 1,
"username": "Alice",
"email": "alice@example.com",
"createdAt": "2024-01-01T12:00:00Z"
}
3. JSON 的优点
- 轻量级:JSON 数据格式简洁,传输效率高。
- 易于解析:大多数编程语言都支持 JSON 的解析和生成。
- 跨语言支持:JSON 是一种通用的数据格式,可以在 JavaScript、Java、Python、C# 等多种语言之间无缝交换数据。
- 易于阅读:JSON 格式接近人类可读的格式,便于调试和开发。
4. JSON 的使用注意事项
- 编码问题:确保 JSON 数据的编码格式(如 UTF-8)一致,避免乱码问题。
- 数据安全:在传输敏感数据时,应使用 HTTPS 加密传输,避免数据被窃取。
- 数据校验:在接收和处理 JSON 数据时,应对数据进行校验,确保数据的完整性和合法性。
- 性能优化:避免传输过大的 JSON 数据,可以对数据进行分页或压缩。
二、什么是 JSON 数据返回类?
为了方便地生成和管理这些 JSON 数据,项目中我们一般会定义一个 JSON 数据返回类。这个类就像是一个“数据包装器”,它把需要返回的信息(比如:状态码、提示信息和实际数据)封装在一起,然后发送给客户端。
- 统一格式:让所有的返回数据都有一个统一的结构,方便客户端解析。
- 方便管理:通过一个类来管理返回的数据,代码更清晰,也更容易维护。
- 提高效率:减少重复代码,开发者只需要调用这个类的方法,就可以快速生成响应。
一个简单的 JSON 数据返回类
假设我们正在开发一个用户管理系统,客户端请求获取用户信息。服务器需要返回用户的数据,或者在失败时返回错误信息。
成功时返回的数据
{
"code": "0",
"msg": "成功",
"data": {
"id": 1,
"username": "Alice",
"email": "alice@example.com"
}
}
失败时返回的数据
{
"code": "1",
"msg": "用户不存在",
"data": null
}
定义类来包装 JSON 数据
public class JsonResponse<T> {
private String code; // 状态码
private String msg; // 提示信息
private T data; // 实际数据
// 成功时的构造方法
public JsonResponse(T data) {
this.code = "0"; // 默认成功状态码
this.msg = "成功"; // 默认成功提示
this.data = data;
}
// 失败时的构造方法
public JsonResponse(String code, String msg) {
this.code = code;
this.msg = msg;
this.data = null; // 失败时没有数据
}
// Getter 方法
public String getCode() { return code; }
public String getMsg() { return msg; }
public T getData() { return data; }
}
使用这个类
假设客户端请求获取用户信息,服务器通过这个类返回数据:
// 成功时
JsonResponse<User> successResponse = new JsonResponse<>(new User(1, "Alice", "alice@example.com"));
// 失败时
JsonResponse<String> failResponse = new JsonResponse<>("1", "用户不存在");
总结
- JSON 数据返回类 是一个工具,用来封装返回给客户端的数据。
- 它包含 状态码(code)、提示信息(msg) 和 实际数据(data)。
- 使用这个类可以让返回的数据格式统一,方便客户端解析和处理。
就像你打包礼物一样,JsonResponse 类把数据“打包”好,然后通过网络发送给客户端。客户端收到后,就可以很方便地打开这个“包裹”,获取里面的内容啦!
三、JSON信息转换配置类
在Spring Boot项目中,默认使用Jackson来处理JSON数据,但在有些场景,使用 FastJSON可能更适合,例如:在处理循环引用、复杂对象序列化或追求更高性能时。
通过这个配置类,我们可以:
- 替换默认的 JSON 转换器为 FastJSON。
- 自定义 JSON 数据的转换规则,满足项目的需求。例如:
- 格式化日期。
- 处理
null值。 - 避免循环引用。
1. 类的定义
@Configuration
public class JsonHttpMessageConverterConfig {
@Configuration:这是一个Spring注解,表明这个类是一个配置类,Spring会扫描并加载这个类中的配置信息。
2. 自定义JSON转换器
2.1 定义一个Bean,返回一个HttpMessageConverters实例
@Bean
@Primary
public HttpMessageConverters fastJsonHttpMessageConverters() {
@Bean:这个注解告诉Spring框架,fastJsonHttpMessageConverters方法的返回值应该被注册为一个Bean。这意味着Spring会调用这个方法,并将返回的HttpMessageConverters实例存储在Spring的上下文中,以便在需要时使用。@Primary:当Spring上下文中存在多个同类型的Bean时,@Primary注解会指示Spring优先使用这个Bean。这在配置多个HttpMessageConverters时特别有用,确保Spring MVC使用的是这个自定义的转换器。
2.2. 创建FastJsonHttpMessageConverter实例
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
- 这里创建了一个
FastJsonHttpMessageConverter的实例。FastJsonHttpMessageConverter是FastJson提供的一个HttpMessageConverter实现,用于处理JSON数据的序列化和反序列化。
2.3. 配置FastJsonConfig
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
fastJsonConfig.setSerializerFeatures(
SerializerFeature.PrettyFormat,
SerializerFeature.WriteNullStringAsEmpty,
SerializerFeature.WriteNullListAsEmpty,
SerializerFeature.WriteMapNullValue,
SerializerFeature.MapSortField,
SerializerFeature.DisableCircularReferenceDetect
);
FastJsonConfig:这是FastJson的配置类,用于设置序列化和反序列化的相关参数。
-
setDateFormat:设置日期格式为yyyy-MM-dd HH:mm:ss。这意味着所有日期类型的字段在序列化为JSON时,都会使用这个格式。setSerializerFeatures:设置序列化特性,具体包括:
-
-
PrettyFormat:输出格式化后的JSON字符串,使JSON更易于阅读。WriteNullStringAsEmpty:将null的字符串字段序列化为空字符串"",而不是null。WriteNullListAsEmpty:将null的列表字段序列化为空列表[],而不是null。WriteMapNullValue:序列化Map时,保留值为null的字段。MapSortField:对Map的字段进行排序。DisableCircularReferenceDetect:禁用循环引用检测。
-
2.4. 将配置应用到FastJsonHttpMessageConverter
fastConverter.setFastJsonConfig(fastJsonConfig);
- 这一步将前面配置好的
FastJsonConfig应用到FastJsonHttpMessageConverter实例中,使得这个转换器在处理JSON数据时会使用这些配置。
2.5. 设置支持的媒体类型
fastConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON));
- 这里设置
FastJsonHttpMessageConverter支持的媒体类型为MediaType.APPLICATION_JSON。这意味着这个转换器只处理JSON格式的数据。这是必要的,因为Spring MVC支持多种媒体类型(如XML、JSON等),通过设置支持的媒体类型,可以确保这个转换器只处理JSON数据。
2.6. 返回HttpMessageConverters实例
return new HttpMessageConverters(fastConverter);
- 最后,创建并返回一个
HttpMessageConverters实例,将配置好的FastJsonHttpMessageConverter添加到其中。Spring会使用这个HttpMessageConverters实例来处理HTTP请求和响应中的JSON数据。
总结
简单来说,这个类就像是一个“翻译规则制定者”,告诉 Spring Boot 如何把 Java 对象翻译成 JSON 数据,或者把 JSON 数据翻译成 Java 对象。
测试
- 启动Spring Boot应用。
- 使用浏览器或Postman访问一个返回JSON的接口,例如
/api/users。 - 检查响应内容是否符合配置:
-
- 日期格式是否为
yyyy-MM-dd HH:mm:ss。 - 是否有格式化(
PrettyFormat)。 - 是否将
null值替换为空字符串或空列表。 - 是否禁用了循环引用检测。
- 日期格式是否为
接口地址:http://localhost:8888/users/14

经过自定义 JSON 转换器之后,返回的格式如下:
{
"createTime": "2025-03-30 00:00:00",
"email": "",
"id": 14,
"name": ""
}
如果,我们把 JSON 转换器去掉,返回的格式是这个样子的:
{
"id": 14,
"name": null,
"email": null,
"createTime": "2025-03-30T00:00:00"
}
扩展:测试循环引用检测的作用。
public static void main(String[] args){
List<Object> list = new ArrayList<>();
Object o = new Object();
list.add(o);
list.add(o);
System.out.println(list.size()); // 输出列表的大小
System.out.println(JSONObject.toJSONString(list)); // 将列表转换为 JSON 字符串
System.out.println(JSONObject.toJSONString(list, SerializerFeature.DisableCircularReferenceDetect)); // 禁用循环引用检测
}
输出结果:
2
[{},{"$ref":"$[0]"}]
[{},{}]
- 默认情况下:
fastjson会检测重复引用,并使用$ref来优化序列化过程,避免重复序列化同一个对象。 - 关闭循环引用检测:使用
SerializerFeature.DisableCircularReferenceDetect后,fastjson不再使用$ref,而是独立序列化每个对象。
关闭循环引用检测之后,又会带来一个新问题?
package org.example.service.config;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.ArrayList;
import java.util.List;
public class CircularReferenceExample {
public static void main(String[] args) {
// 创建两个对象,形成循环引用
Person personA = new Person("Alice");
Person personB = new Person("Bob");
// 让 personA 引用 personB
personA.setFriend(personB);
// 让 personB 引用 personA
personB.setFriend(personA);
// 将 personA 添加到列表中
List<Person> list = new ArrayList<>();
list.add(personA);
// 输出列表的大小
System.out.println("列表大小: " + list.size()); // 输出 1
// 将列表转换为 JSON 字符串(默认情况下会检测循环引用)
System.out.println("默认情况下序列化:");
System.out.println(JSONObject.toJSONString(list));
// 禁用循环引用检测
System.out.println("禁用循环引用检测后的 JSON 字符串:");
System.out.println(JSONObject.toJSONString(list, SerializerFeature.DisableCircularReferenceDetect));
}
}
class Person {
private String name;
private Person friend;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getFriend() {
return friend;
}
public void setFriend(Person friend) {
this.friend = friend;
}
}

关闭循环引用检测的场景
- 避免重复引用的
ref显示:在某些情况下,开发者可能不希望在序列化后的JSON数据中出现重复引用的“ref”标识,关闭循环引用检测可以实现这一点。 - 提升性能:关闭循环引用检测能够提升序列化时的性能。
不关闭循环引用检测的场景
- 防止栈溢出异常:如果存在循环引用,关闭循环引用检测可能会导致
StackOverflowError异常。因为循环引用检测是 FastJSON 提供的一种避免运行时异常的机制,如果关闭它,循环引用时就无法检测到这种异常情况。 - 保持数据的正确性:开启循环引用检测可以正确处理循环引用的情况,确保序列化和反序列化的结果符合预期。
总结
在实际项目开发中,保持循环引用检测开启是更安全和更常见的选择。如果确实需要关闭循环引用检测,应该谨慎评估其风险,并确保代码中不存在循环引用。
在 Spring Boot 项目中,可以通过局部配置来灵活处理循环引用检测。例如,可以在特定的序列化场景中关闭循环引用检测,而不影响其他部分。
那么如何避免重复引用的ref显示呢?避免多处引用同一个对象
在 Java 编程中,对象是通过引用传递的。如果你在多个地方引用同一个对象,修改一个地方的对象可能会导致其他地方的引用也发生变化,这可能会引发一些难以察觉的错误。特别是在使用 FastJSON 进行序列化时,这种问题可能会更加明显。
示例
假设你有以下代码:
public class Person {
private String name;
private List<String> hobbies;
// 构造函数、getter 和 setter 省略
}
public class Main {
public static void main(String[] args) {
List<String> hobbies = new ArrayList<>();
hobbies.add("Reading");
Person person1 = new Person("Alice", hobbies);
Person person2 = new Person("Bob", hobbies);
// 修改 person2 的 hobbies
person2.getHobbies().add("Swimming");
System.out.println(person1.getHobbies()); // 输出:[Reading, Swimming]
System.out.println(person2.getHobbies()); // 输出:[Reading, Swimming]
}
}
在这个例子中,person1 和 person2 共享同一个 hobbies 列表。当你修改 person2 的 hobbies 时,person1 的 hobbies 也会被修改,这可能不是你期望的结果。
改进
为了避免这种情况,可以在创建对象时使用新的集合或对象:
Person person1 = new Person("Alice", new ArrayList<>(hobbies));
Person person2 = new Person("Bob", new ArrayList<>(hobbies));
这样,每个 Person 对象都有自己的 hobbies 列表,互不影响。
680

被折叠的 条评论
为什么被折叠?



