JSON数据返回类,自定义JSON转换器

一、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 的优点

  1. 轻量级:JSON 数据格式简洁,传输效率高。
  2. 易于解析:大多数编程语言都支持 JSON 的解析和生成。
  3. 跨语言支持:JSON 是一种通用的数据格式,可以在 JavaScript、Java、Python、C# 等多种语言之间无缝交换数据。
  4. 易于阅读:JSON 格式接近人类可读的格式,便于调试和开发。

4. JSON 的使用注意事项

  1. 编码问题:确保 JSON 数据的编码格式(如 UTF-8)一致,避免乱码问题。
  2. 数据安全:在传输敏感数据时,应使用 HTTPS 加密传输,避免数据被窃取。
  3. 数据校验:在接收和处理 JSON 数据时,应对数据进行校验,确保数据的完整性和合法性。
  4. 性能优化:避免传输过大的 JSON 数据,可以对数据进行分页或压缩。

二、什么是 JSON 数据返回类?

为了方便地生成和管理这些 JSON 数据,项目中我们一般会定义一个 JSON 数据返回类。这个类就像是一个“数据包装器”,它把需要返回的信息(比如:状态码、提示信息和实际数据)封装在一起,然后发送给客户端。

  1. 统一格式:让所有的返回数据都有一个统一的结构,方便客户端解析。
  2. 方便管理:通过一个类来管理返回的数据,代码更清晰,也更容易维护。
  3. 提高效率:减少重复代码,开发者只需要调用这个类的方法,就可以快速生成响应。

一个简单的 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可能更适合,例如:在处理循环引用、复杂对象序列化或追求更高性能时。

通过这个配置类,我们可以:

  1. 替换默认的 JSON 转换器为 FastJSON。
  2. 自定义 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 对象。

测试

  1. 启动Spring Boot应用。
  2. 使用浏览器或Postman访问一个返回JSON的接口,例如 /api/users
  3. 检查响应内容是否符合配置:
    • 日期格式是否为 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]
    }
}

在这个例子中,person1person2 共享同一个 hobbies 列表。当你修改 person2hobbies 时,person1hobbies 也会被修改,这可能不是你期望的结果。

改进

为了避免这种情况,可以在创建对象时使用新的集合或对象:

Person person1 = new Person("Alice", new ArrayList<>(hobbies));
Person person2 = new Person("Bob", new ArrayList<>(hobbies));

这样,每个 Person 对象都有自己的 hobbies 列表,互不影响。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值