Spring Boot 将 JSON 中的 Long 值序列化为 String 避免精度丢失
什么是精度丢失?
Java 中长整形 Long
(64位)的取值范围是:-9223372036854775808
- 9223372036854775807
。
在这种情况下,由于 JavaScript 的 Number
类型是 64 位浮点数,它无法精确表示超过 53 位的整数。因此,当将 Java Long
类型的值传递给 JavaScript 时,可能会发生精度丢失。
你可以在浏览器控制台运行如下代码,更直观地感受 “精度丢失” 的问题。
let val = 9223372036854775807;
console.log(val); //9223372036854776000 输出的值丢失了精度
如果我们在业务中使用 Long
作为数据类型,那么就必须要考虑浏览器客户端中 Js 存在精度丢失的问题。解决这个问题最简单的办法就是 把 Java 的 Long 值,序列化为字符串传递。
Jackson 的注解支持
Spring Boot 默认使用 Jackson 作为 JSON 的序列化、反序列化框架。Jackson 提供了 @JsonSerialize
注解,该注解的 using
属性可以指定一个 JsonSerializer
的实现类,用于自定义字段的序列化方式。
Jackson 已经预定义了一个实现 ToStringSerializer
,用于把指定的字段序列化为字符串。
定义一个简单的 User
对象:
package cn.springdoc.demo.model;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
public class User {
// 把 Long 类型的 id 序列化为 字符串
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String name;
// 忽略 get / set 方法
}
定义一个 Controller 进行测试:
package cn.springdoc.demo.web.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.springdoc.demo.model.User;
@RestController
@RequestMapping("/demo")
public class DemoController {
@PostMapping
public User demo (@RequestBody User user) {
return user;
}
}
如上,定义一个简单的端点,接收客户端 POST 的 JSON 字符串,封装为 User
对象后原样返回。
启动应用,使用 curl 进行测试:
$ curl -X POST -H "Content-Type: application/json" -d '{"id": 9223372036854775807, "name": "springdoc.cn"}' http://localhost:8080/demo
{"id":"9223372036854775807","name":"springdoc.cn"}
如你所见,请求体中的 id
字段是一个数值类型,其值为 9223372036854775807
,也就是 Long 的最大值。Jackson 准确地把请求体封装为了 User
对象。
并且在响应 JSON 中,id
字段类型是 String
,说明注解生效!
id
字段如果以字符串形式传递,Jackson 也能自动解析为 Long
类型:
$ curl -X POST -H "Content-Type: application/json" -d '{"id": "9223372036854775807", "name": "springdoc.cn"}' http://localhost:8080/demo
{"id":"9223372036854775807","name":"springdoc.cn"}
全局设置
@JsonSerialize
注解的弊端在于,需要对所有 Long
类型的字段进行一一设置,这太麻烦。
通过 Spring Boot 的 Jackson2ObjectMapperBuilderCustomizer
配置类,可以对 Jackson 进自定义,从而指定所有 Long
类型的序列化方式为 String
。
package cn.springdoc.demo.configuration;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@Configuration
public class JacksonConfiguration {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
// 把 Long 类型序列化为 String
builder.serializerByType(Long.class, ToStringSerializer.instance);
};
}
}
删除 User
对象中的注解:
package cn.springdoc.demo.model;
public class User {
private Long id;
private String name;
// 省略 get / set 方法
}
重启应用,再次使用 curl 进行测试:
$ curl -X POST -H "Content-Type: application/json" -d '{"id": "9223372036854775807", "name": "springdoc.cn"}' http://localhost:8080/demo
{"id":"9223372036854775807","name":"springdoc.cn"}
全局设置成功。系统中所有的 Long
类型,都会被序列化为 String
。