Spring Boot 返回 JSON 数据及数据封装

在项目开发中,接口与接口之间,前后端之间的数据传输都使用JSON格式,总结Spring Boot 是怎么返回 JSON 数据的,核心内容如下:

  • @RestController 注解
  • 常用数据类型转为 JSON 格式
  • 使用 fastjson 封装统一返回的数据结构

@RestController 注解

在Spring Boot中,接口返回JSON格式的数据很简单,在Controller中使用@RestController注释即可返回JSON格式的数据

@RestController 也是 Spring Boot 新增的一个注解,我们点进去看一下该注解都包含了哪些东西。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    String value() default "";
}

可以看出,@RestController 注解包含了原来的 @Controller@ResponseBody 注解。使用过 Spring 的朋友对 @Controller 注解已经非常了解了。@ResponseBody 注解是将返回的数据结构转换为 JSON 格式。

所以在默认情况下,使用了 @RestController 注解即可将返回的数据结构转换成 JSON 格式,在 Spring Boot 中默认使用的 JSON 解析技术框架是 Jackson。

我们点开 pom.xml 中的 spring-boot-starter-web 依赖,可以看到 spring-boot-starter-json 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.0.3.RELEASE</version>
    <scope>compile</scope>
</dependency>

Spring Boot 对依赖都做了很好的封装,可以看到很多 spring-boot-starter-xxx 系列的依赖,这是 Spring Boot 的特点之一,不需要人为引入很多相关的依赖,starter-xxx 系列直接包含了所必要的依赖,所以我们再次点进去上面提到的 spring-boot-starter-json 依赖,可以看到如下代码:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.6</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>2.9.6</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.6</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
    <version>2.9.6</version>
    <scope>compile</scope>
</dependency>

到此为止,我们知道了 Spring Boot 中默认使用的 JSON 解析框架是 Jackson。下面我们看一下默认的 Jackson 框架对常用数据类型的转 JSON 处理。

常用数据类型转为JSON格式

在实际项目中,常用的数据结构无非有类对象、List 对象、Map 对象,我们看一下默认的 Jackson 框架如何将这三个常用的数据结构转成 JSON 格式的。

1. 创建User实体类
为了测试,我们需要创建一个实体类,这里我们就用User 来演示:

public class User {
    private Long id;
    private String username;
    private String password;
    /* 省略get、set和带参构造方法 */
}

2. 创建Controller类
然后创建一个Controller,分别返回User对象,List和Map<String ,Object>.

package com.example.demo.controller;


import com.example.demo.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/Json")
public class JsonController {
    @RequestMapping("/user")
    public User getUser(){
        return new User( 1,"张三","12345");
    }
    @RequestMapping("/list")
    public List<User> getUserList(){
        ArrayList<User> userList = new ArrayList<>();
        User user1 = new User(1,"张三","12345");
        User user2 = new User(2,"李四","12345");
        User user3 =new User(3,"王五","12345");
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);
        return userList;
    }

    @RequestMapping("/map")
    public Map<String,Object> getMap(){
        Map<String,Object>map = new HashMap<>();
        User user = new User(1,"张三","12345");
        map.put("个人信息",user);
        map.put("年龄",20);
        map.put("国籍","中国");
        map.put("手机号","1234567890");
        return map;
    }
}

3.测试不同数据类型返回的JSON
Controller 接口写好,分别返回了一个User对象,一个List集合 和 一个Map集合,.接下来我们依次测试一下效果.启动项目:
在浏览器输入:http://localhost:8081/Json/user ,返回Json如下:

{"id":1,"username":"张三","password":"12345"}

在浏览器输入:http://localhost:8081/Json/list,返回Json如下:

[{"id":1,"username":"张三","password":"12345"},{"id":2,"username":"李四","password":"12345"},{"id":3,"username":"王五","password":"12345"}]

在浏览器输入:http://localhost:8081/Json/map,返回Json如下:

{"手机号":"1234567890","个人信息":{"id":1,"username":"张三","password":"12345"},"国籍":"中国","年龄":20}

Jackson 中对null的处理
在实际情况中,我们难免会遇到一些null值.当我们转JSON时,不希望这些null出现,比如我们期望所有的null在转JSON的时候都变成" "这种空的字符串,这个该如何去完成呢?

在SpringBoot 中,我们做一下配置即可,新建一个config包,在包内创建一个JacksonConfig的配置类:

package com.example.demo.config;
@Configuration
public class JacksonConfig {
    @Bean
    @Primary
    @ConditionalOnMissingBean(ObjectMapper.class)
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
            @Override
            public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                jsonGenerator.writeString("");
            }
        });
        return objectMapper;
    }

}

添加一个controller接口,进行测试:

@RequestMapping("/map1")
    public Map<String, Object> getMap1() {
        Map<String, Object> map1 = new HashMap<>(3);
        User user = new User(1, "张三", null);
        map1.put("个人信息", user);
        map1.put("年龄",20);
        map1.put("国籍","中国");
        map1.put("手机号",null);
        return map1;
    }

重启项目,在浏览器中输入:http://localhost:8081/Json/map1,返回json如下:

{"国籍":"中国","年龄":20,"手机号":"","个人信息":{"id":1,"username":"张三","password":""}}

这个可以看到,返回的并不是null,而是" " 空字符串

使用fastjson

1.Jackson和fastjson比较

java 返回json后台报错 返回json数据类型_spring


大家可以根据自己实际项目情况选择合适的框架。从扩展上来看,fastjson 没有 Jackson 灵活,从速度或者上手难度来看,fastjson 可以考虑,它也比较方便。

fastjson依赖导入
使用fastjson需要导入的依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.35</version>
</dependency>

使用fastjson 处理null
使用 fastjson 时,对 null 的处理和 Jackson 有些不同,需要继承 WebMvcConfigurationSupport 类,然后覆盖 configureMessageConverters 方法。在方法中,我们可以选择要实现 null 转换的场景,配置好即可。代码如下:

@Configuration
public class fastJsonConfig extends WebMvcConfigurationSupport {

    /**
     * 使用阿里 fastjson 作为 JSON MessageConverter
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
        FastJsonConfig config = new FastJsonConfig();
        config.setSerializerFeatures(
                // 保留 Map 空的字段
                SerializerFeature.WriteMapNullValue,
                // 将 String 类型的 null 转成""
                SerializerFeature.WriteNullStringAsEmpty,
                // 将 Number 类型的 null 转成 0
                SerializerFeature.WriteNullNumberAsZero,
                // 将 List 类型的 null 转成 []
                SerializerFeature.WriteNullListAsEmpty,
                // 将 Boolean 类型的 null 转成 false
                SerializerFeature.WriteNullBooleanAsFalse,
                // 避免循环引用
                SerializerFeature.DisableCircularReferenceDetect);

        converter.setFastJsonConfig(config);
        converter.setDefaultCharset(Charset.forName("UTF-8"));
        List<MediaType> mediaTypeList = new ArrayList<>();
        // 解决中文乱码问题,相当于在 Controller 上的 @RequestMapping 中加了个属性 produces = "application/json"
        mediaTypeList.add(MediaType.APPLICATION_JSON);
        converter.setSupportedMediaTypes(mediaTypeList);
        converters.add(converter);
    }
}

封装统一返回的数据结构
以上展示了 Spring Boot 返回 JSON 的代表案例,但在实际项目中,除了要封装数据之外,我们往往需要在返回的 JSON 中添加一些其他信息,比如返回状态码 Code,返回 Msg 给调用者,调用者可以根据 Code 或者 Msg 进行一些逻辑判断。所以在实际项目中,我们需要封装一个统一的 JSON 返回结构存储返回信息。

定义统一的JSON结构
由于封装的JSON数据的类型不确定,所以在定义统一的Json结构时,我们需要用到泛型.

统一的JSON结构中属性包括数据,状态码,提示信息即可,构造方法可以根据实际业务需求做相应的添加.一般来说,应该有默认的返回值,也应该有用户指定的返回结构.

public class JsonResult<T> {

    private T data;
    private String code;
    private String msg;

    /**
     * 若没有数据返回,默认状态码为 0,提示信息为“操作成功!”
     */
    public JsonResult() {
        this.code = "0";
        this.msg = "操作成功!";
    }

    /**
     * 若没有数据返回,可以人为指定状态码和提示信息
     * @param code
     * @param msg
     */
    public JsonResult(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    /**
     * 有数据返回时,状态码为 0,默认提示信息为“操作成功!”
     * @param data
     */
    public JsonResult(T data) {
        this.data = data;
        this.code = "0";
        this.msg = "操作成功!";
    }

    /**
     * 有数据返回,状态码为 0,人为指定提示信息
     * @param data
     * @param msg
     */
    public JsonResult(T data, String msg) {
        this.data = data;
        this.code = "0";
        this.msg = msg;
    }
    // 省略 get 和 set 方法
}

修改Controller中的返回值类型及测试
因为JsonResult 使用了泛型,所以使用的返回值类型都可以使用该统一结构.在具体的场景将泛型替换成具体的数据类型,非常方便,也便于维护.在实际项目中,还可以继续封装,比如状态码和提示信息可以定义一个枚举类型,以后我们只需要维护这个枚举类型中的数据即可.根据以上的JsonResult,我们改写一下Controller,如下:

@RestController
@RequestMapping("/jsonresult")
public class JsonResultController {

    @RequestMapping("/user")
    public JsonResult<User> getUser() {
        User user = new User(1, "张三", "123456");
        return new JsonResult<>(user);
    }

    @RequestMapping("/list")
    public JsonResult<List> getUserList() {
        List<User> userList = new ArrayList<>();
        User user1 = new User(1, "张三", "123456");
        User user2 = new User(2, "李四", "123456");
        userList.add(user1);
        userList.add(user2);
        return new JsonResult<>(userList, "获取用户列表成功");
    }

    @RequestMapping("/map")
    public JsonResult<Map> getMap() {
        Map<String, Object> map = new HashMap<>(3);
        User user = new User(1, "张三", null);
         map1.put("个人信息", user);
        map1.put("年龄",20);
        map1.put("国籍","中国");
        map1.put("手机号",null);
        return new JsonResult<>(map);
    }
}

我们重新在浏览器中输入:localhost:8080/jsonresult/user,返回 JSON 如下:

{"code":"0","data":{"id":1,"password":"123456","username":"张三"},"msg":"操作成功!"}

输入:localhost:8080/jsonresult/list,返回 JSON 如下:

{"code":"0","data":[{"id":1,"password":"123456","username":"张三"},{"id":2,"password":"123456","username":"李四"}],"msg":"获取用户列表成功"}

输入:localhost:8080/jsonresult/map,返回 JSON 如下:

{"code":"0","data":{"国籍":"中国","年龄":20,"手机号":"","个人信息":{"id":1,"username":"张三","password":""}}

通过封装,我们不但将数据通过JSON传给前端或者其他接口,还带上了状态码和提示信息,这在实际项目场景中应用的非常广泛.