前后端数据传输格式(下)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

上篇主要复习了HTTP以及POST相关的几种传参形式,这一篇来讲讲和实际开发更为紧密的内容:JSON。

初学者在需求分析阶段通常缺乏以下能力:

  • 无法通过看页面原型图分析出大致的请求格式和响应格式
  • 即使能分析出请求格式,却不知道用什么样的Java对象接收
  • 即使能分析出响应格式,却不知道用什么样的Java对象返回

这三个能力,其实都依赖于你对JSON的理解(本文主要讨论JSON,不涉及表单请求和文件上传类型)。

说得更具体些就是,初学者往往搞不清楚JSON与Java对象的转换关系(图片来自尚硅谷):

  • 请求:JSON转Java
  • 响应:Java转JSON

JSON

JSON的作用

在我刚入行时写过相关的博客,比较简单,感兴趣的同学可以看看:AJAX与JSON

JSON简单来说就是特定格式的字符串,注意,它的载体是字符串。很多人分不清JSON和JS对象,其实两者没有可比性。实在要说的话,JS对象和Java对象都是正儿八经的对象,存活于内存中(浏览器/服务器),而JSON只是字符串,往往承担的是网络传输的角色:

我们都知道,网页上的动态数据都是服务器那边组装后通过HTTP返回的,但很少有人会思考这样的一个问题:

服务器组装数据时用的是Java对象,比如user.setName("bravo"),但前端得到的却是JS对象,怎么做到的?

一般来说,浏览器被安装在我们的个人电脑中,而提供服务的Java应用则可能运行在阿里云服务器上,两者并不在同一片物理内存。由于对象存活必须依赖于内存(如果不做持久化,只要一断电,内存中的数据就没了),所以就上图而言,Java对象显然没法自己“跳出”服务器进入浏览器中,更别提Java对象如何变成JS对象。

要想完成跨内存的数据传输、对象转换,必须通过网络传输,而且需要一个传递信息的载体,过程中还涉及到序列化和反序列化。

实际上,整个请求响应的过程就是序列化和反序列化的过程:

  • 服务器端把Java对象序列化为JSON(中介,特定格式的字符串)
  • 网络不能直接传递对象,但可以传输字符串
  • 浏览器得到JSON后,反序列化为JS对象,然后设置到页面上

同理,不仅是浏览器和服务器,服务器和服务器之间也需要JSON作为数据交互的中介:

JSON的格式

使用Postman时,我们经常会发这样的请求:

Body就是请求体内容,一般都是字符串格式,所以这是一个JSON,而不是JS对象,尽管它们看起来很像。你可以理解为JSON是多种语言共同协定的一种数据交互格式:

  • "field":"xxx" 表示对象的字段,value如果是字符类型需要加"",数值可以不加
  • {} 表示对象或map或其他符合key-value格式的结构
  • [] 表示一组对象、一组字符串或一组数值

很简单吧?

然后各个语言都会遵守这个协定,转化为自己的对象结构,比如:

  • {}可以代表Java对象/Map,[]由于表示一组数据,刚好可以对应Java的数组、List或Set等单列集合
  • {}也可以代表Python对象/字典,[]对应Python的元组或list等
  • {}还可以代表PHP的对象,[]对应PHP中的Array
  • ...

当然,{}也可以代表JS对象,[]则可以转化为JS的数组。

为什么很多初学者会搞混JSON和JS对象呢?本质上还是因为:

  • 初学者都“见过”JS对象,而且它往往都是{}的形式出现
  • JS对象的格式和JSON确实比较像

但JSON本质是“干瘪瘪”的字符串,当各大语言需要进行反序列化时,就会按照上面的格式转为内存中“圆鼓鼓”的对象。应该把JS对象和Java对象看作一个梯队,而JSON则在另一个梯队,是一个特定格式的字符串。

常见JSON格式与Java对象的转换

这里只演示Java对象与JSON的转换:

如果不信的话,可以自己动手写一下接口,然后用Postman按照图中的格式发送JSON,看看接口能不能顺利接收参数。

上面说过,JSON的{}可以对应Java的对象或者Map,{}两个括号表示对象的边界,其实刚好对应类的{},里面的就是对象的字段。

我们来分析一下第一张图的结构。

接口返回的是一个HashMap,所以很容易想到最终JSON格式是:

{

...

}

接着往Map里put了一些value,我们先不管value是什么类型:

{

"1号男嘉宾" : xxx,

"2号男嘉宾" : xxx,

"3号男嘉宾" : xxx,

}

OK,JSON的大致结构出来了,再深入一层,看看xxx是什么。很明显,是一个Java的User对象,还是对应{}:

{

"1号男嘉宾" : {},

"2号男嘉宾" : {},

"3号男嘉宾" : {},

}

那么,这个User对象有哪些字段呢?填上即可:

{
    "1号男嘉宾": {
        "name": "雅木茶",
        "age": 23
    },
    "2号男嘉宾": {
        "name": "卡卡罗特",
        "age": 23
    },
    "3号男嘉宾": {
        "name": "贝吉塔",
        "age": 22
    }
}

其他两个分析过程同理,就不演示了(思考一下,如果User里面有Department会是什么样)。

刚才是顺着来,现在我们玩一下“逆推”。假设现在前端跑过来告诉你

大佬,这个接口我到时候这样传参给你行吗?

[
    {
        "name": "张飞",
        "age": 18,
        "tags": [
            "大眼睛",
            "大胡子"
        ]
    },
    {
        "name": "关羽",
        "age": 19,
        "tags": [
            "万人敌",
            "长胡子"
        ]
    },
    {
        "name": "刘备",
        "age": 20,
        "tags": [
            "刘皇叔",
            "摔阿斗"
        ]
    }
]

此时你应该如何设计入参才能接受前端这种格式的JSON呢?

jackson的一些操作

之前介绍过,服务器本身没有能力处理JSON和文件上传,都要靠第三方组件。SpringBoot则引入了jackson作为默认的JSON组件,其他常见的还有阿里的fastJson和谷歌的gson。

这里介绍一下jackson常见的几个注解。

@Slf4j
@RestController
public class UserController {

    @PostMapping("/addUser")
    public UserPOJO addUser(@RequestBody UserPOJO user) {
        user.setAge(null);
        return user;
    }

}

@Data
public class UserPOJO {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 用户类型
     */
    private Integer userType;
    /**
     * 个人标签
     */
    private List<String> tags;

}

@JsonInclude

有时我们希望如果字段为null就不要返回给前端,可以使用@JsonInclude,它可以指定很多属性。

@JsonInclude还可以加在类上,那么该对象所有为null的字段都不会参与JSON序列化。

@JsonProperty

对于一些老项目或者其他什么原因,原本传参使用的是下划线,比如user_type,而后端用Java改写时又要符合驼峰命名,此时可以用@JsonProperty做一层“隔离”。

此时出入参都必须叫user_type:

@JsonFormat

有些同学容易把@JsonFormat和@DateTimeFormat搞混,我们单独开一个小节聊一聊。

时间格式

首先和大家说一下,数据库字段无论是datetime还是timestamp,其实都是可以自动对应Java的Date对象,一般讨论的所谓时间格式,都是指前端的显示格式:

刚才我们讨论为什么需要JSON时,提到一个观点:对象是在内存中存活的,无法直接进行网络传输。但是大家有没有想过:

class User {
    private String name;
    private Date birthday;
}

其实里面的字段也是对象,也要进行序列化。SpringBoot引入了jackson作为JSON序列化的组件,其中必然包括对Date进行序列化/反序列化的方案。

然而,SpringBoot1.x和2.x其实有较大的改动,其中就包括对Date格式化的改动。大家可以沿用刚才的项目,给UserPOJO加上birthday字段,然后在SpringBoot1.5.9和SpringBoot2.3.4环境下实验。

@Slf4j
@RestController
public class UserController {

    @PostMapping("/addUser")
    public UserPOJO addUser(@RequestBody UserPOJO user) {
        return user;
    }

}

SpringBoot1.x

SpringBoot2.x

有两个细节:

  • SpringBoot1.x的返回值是毫秒数,SpringBoot2.x是另一种格式
  • 当入参和出参时间格式不同时,会发生转换,此时会出现时间差
    • SpringBoot1.x传递2020-12-07T22:58:11.000+00:00,返回1607381891000(+8)
    • SpringBoot2.x传递1607353091000,返回2020-12-07T14:58:11.000+00:00(-8)

可以通过时间戳转换验证一下(注意单位)。

时差的问题可以通过配置解决,比如:

spring.jackson.time-zone=GMT+8

总的来说就是:

如果希望更改出入参的时间格式,可以有局部和全局两种方式:

  • 局部:@JsonFormat / @DateTimeFormat
  • 全局
    • YAML
    • Config

@DateTimeFormat只适用于非JSON的POST请求,也就是说,如果项目本身都是JSON请求,你用@DateTimeFormat是无效的。你可以简单理解为:

  • @DateTimeFormat,走表单请求时间转换器(适用于GET、POST表单请求)
  • @JsonFormat,走JSON请求时间转换器(适用于POST JSON请求、JSON响应)

所以,你在这煞费苦心地调整表单请求的转换格式有啥用?

这里演示一下@JsonFormat的用法:

/**
 * 生日(时间格式很容易写错,可以抽取为常量或者使用第三方提供的,比如hutool就有)
 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date birthday;

此时出入参都变为指定格式:

也可以在YAML中进行全局配置:

上面那个mvc:date-format是对表单请求的配置。

或者使用Config:

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        // 全局配置
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return objectMapper;
    }

}

如果说@JsonInclude/@JsonFormat加到字段上、类上分别是字段级别、类级别,那么上面的配置就是整个项目级别,因为Spring的bean默认单例,而这个唯一的ObjectMapper已经被做了手脚,最终所有接口的序列化/反序列化行为都被改写。

这种全局和局部的思想,后面会很常见,这里先点一下。

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/214911.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 的 NULL 是怎么存储的?

目录 一、MySQL介绍 二、什么是NULL 三、MySQL 的 NULL 是怎么存储的 一、MySQL介绍 MySQL是一种关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是一种开源软件&#xff0c;由瑞典MySQL AB公司开发&#xff0c;后被Sun Microsystems收购&#xff0c;现在…

阿里云租赁费用_阿里云服务器多配置报价表

阿里云服务器租用费用&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、轻量应用服务器2核2G3M带宽轻量服务器一年87元&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;ECS云服务器e系列2核2G配置99元一年、2核4G配置365元一年、2核8G配置522元一年…

组网技术-交换机

交换机&#xff1a; 分类&#xff1a; 根据交换方式划分&#xff1a; 1.存储转发交换&#xff1a;交换机对输入的数据包先进行缓存、验证、碎片过滤&#xff0c;然后进行转发。 时延大&#xff0c;但是可以提供差错校验&#xff0c;并支持不同速度的输入、输出端口间的交换…

【Altera】Cyclone10 FPGA DDR3使用

目录 开发板 硬件 框图 原理图 测试工具 DDR IP核配置 调试及遇到的问题 读写仲裁时序 问题1.拉高read后&#xff0c;wait一直没反应 问题2.DDR校正不过的一个可能性 延伸学习 开发板 Intel官方提供c10的开发套件&#xff1a;Intel Cyclone 10 GX FPGA Development …

算法基础--双指针

前面已经写了两篇关于算法方面的文章&#xff0c;这几天想了下&#xff0c;决定把这个算法整理成一个系列&#xff0c;除了是帮助自己巩固算法知识外&#xff0c;还能够把自己总结的每种算法的套路保存下来并分享给大家&#xff0c;这样以后即使是哪天想要重拾起来&#xff0c;…

数据结构——链表

目录 一、链表的定义 二、链表的基本操作 1. 单链表 1.1. 定义链表结点结构体 1.2. 定义链表类 1.3. 创建链表 1.3.1. 头插法创建链表 1.3.2. 尾插法创建链表 1.4. 遍历链表 1.5. 查找结点 1.6. 插入结点 1.7. 删除结点 1.8. 删除链表 1.9. 完整的单链表类 2. 双…

GeoServer漏洞(CVE-2023-25157)

前半部分是sql注入一些语句的测试&#xff0c;后面是漏洞的复现和利用 Sql注入漏洞 1.登入mysql。 2.查看默认数据库 3.使用mysql数据库 4.查看表 1.查看user 表 2.写注入语句 创建数据库 时间注入语句 布尔注入语句 报错注入语句 Geoserver漏洞&#xff…

45 - 多线程性能优化常见问题

1、使用系统命令查看上下文切换 上下文切换常见的监测工具 1.1、Linux 命令行工具之 vmstat 命令 vmstat 是一款指定采样周期和次数的功能性监测工具&#xff0c;我们可以使用它监控进程上下文切换的情况。 vmstat 1 3 命令行代表每秒收集一次性能指标&#xff0c;总共获取 …

Apache Doris 详细教程(二)

5、doris的查询语法 5.1、doris查询语法整体结构 SELECT [ALL | DISTINCT | DISTINCTROW ] -- 对查询字段的结果是否需要去重&#xff0c;还是全部保留等参数 select_expr [, select_expr ...] -- select的查询字段 [FROM table_references [PARTITION…

遥感方向期刊总结

开眼看期刊~ 期刊分区信息搜索网址中国科学院文献情报中心期刊分区表升级版科研通期刊查询&#xff1a; 遥感领域常见期刊Nature CommunicationsRemote Sensing of EnvironmentProceedings of the National Academy of Sciences &#xff08;PNAS&#xff09;ISPRS Journal of …

WebUI工作流插件超越ComfyUI

在AI绘画领域&#xff0c;Stable Diffsion是最受欢迎的&#xff0c;因为它是开源软件。 开源有两大优势&#xff0c;一是免费&#xff0c;二是适合折腾。 大量的开发者、爱好者投入无尽的热情&#xff0c;来推动Stable Diffsion的快速发展。 在图形界面方面&#xff0c;WebU…

排序算法介绍(一)插入排序

0. 简介 插入排序&#xff08;Insertion Sort&#xff09; 是一种简单直观的排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常…

prometheus|云原生|轻型日志收集系统loki+promtail的部署说明

一&#xff0c; 日志聚合的概念说明 日志------ 每一个程序&#xff0c;服务都应该有保留日志&#xff0c;日志的作用第一是记录程序运行的情况&#xff0c;在出错的时候能够记录错误情况&#xff0c;简单来说就是审计工作&#xff0c;例如nginx服务的日志&#xff0c;kuber…

04-数据库操作对象Statement对象和PreparedStatement对象的区别,SQL注入的优缺点

Statement对象和查询结果集 Statement对象相关的方法 Connection接口中获取数据库操作对象Statement对象的方法 方法名功能Statement createStatement()创建Statement对象 Statement对象执行增删改查的SQL语句(不含占位符"?")的方法,JDBC中的SQL语句不需要提供分…

基于 ESP32 的带触摸显示屏的 RFID 读取器

如何设计一款基于 ESP32 且具有 ILI9341 触摸屏显示屏且适合壁挂式安装的美观 RFID 读取器。 本项目中用到的东西 硬件组件 ESP32 开发套件 C 1 AZ-Touch ESP 套件 1 RFID-RC522 IC卡读写器 1 ​编辑 电线、绕包线 1 详细设计流程 …

机器学习 - 导论

简单了解 机器学习关于数据集的概念 、

Autosar COM通信PDU

文章目录 Autosar 中各个PDU所在示意图PDU的分类PDU 和 SDU 的关系I-PDUN-PDUL-PDU相关协议其他参考 Autosar 中各个PDU所在示意图 PDU的分类 在Autosar 中&#xff0c;主要有 I-PDU、N-PDU和 L-PDU 三种。 L-PDU&#xff1a;Data Link Layer PDU&#xff0c;数据链路层PDUN-…

Spring-Boot---项目创建和使用

文章目录 什么是Spring-Boot&#xff1f;Spring-Boot项目的创建使用Idea创建使用网页创建 项目目录介绍项目启动 什么是Spring-Boot&#xff1f; Spring的诞生是为了简化Java程序开发的&#xff1b;而Spring-Boot的诞生是为了简化Spring程序开发的。 Spring-Boot具有很多优点…

知识点滴 - 什么是半透膜和渗透压

半透膜和渗透作用 1748年的一天&#xff0c;法国物理学家诺勒为了改进酒的制作水平&#xff0c;设计了这样一个试验&#xff1a;在一个玻璃圆筒中装满酒精&#xff0c;用猪膀胱封住&#xff0c;然后把圆筒全部浸在水中。当他正要做下一步的工作时&#xff0c;突然发现&#xff…

巧用JAVA自带的API解决日期类问题

文章目录 题目代码优势 题目 特殊日期 代码 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改 import java.time.LocalDate; public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);//在此输入您的代…