有道无术,术尚可求,有术无道,止于术。
本系列Jackson 版本 2.17.0
本系列Spring Boot 版本 3.2.4
源码地址:https://gitee.com/pearl-organization/study-jaskson-demo
文章目录
- 1. 问题场景
- 2. 原因分析
- 3. 解决方案
- 4. 案例演示
- 4.1 方式一:使用注解
- 4.2 方式二:全局配置
- 4.2.1 方案分析
- 4.2.2 配置
1. 问题场景
当前用户表主键ID
采用雪花算法生成,实体类对应的类型为Long
:
@ApiModelProperty(value = "主键ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
返回前端时,发现Long
精度丢失,例如ID
为362909601374617692
时,前端拿到的值却是362909601374617660
,导致ID
错误查询不到当前数据,甚至可能查询到了错误的数据。
2. 原因分析
在Java
中,long
是一种基本数据类型,用于表示整数,它属于8
字节(64
位)的有符号类型,最小值为-2
的63
次方,即-9223372036854775808
,最大值为2
的63
次方-1
,即9223372036854775807
。
在JavaScript
中,Number
类型是基本数据类型之一,用于表示数字,可以包含整数、浮点数、负数等各种数值类型。整数表示时的最小值为-2
的53
次方-1
,即-9007199254740991
,最大值为2
的53
次方-1
,即9007199254740991
。
Java
直接返回Long
整型数据给前端时,JS
会自动转换为Number
类型,从下面的对比中,可以很直观的看到,当超过JS
的整数范围时,该数值会精度损失。
-9223372036854775808 // long
-9007199254740991 // js
9223372036854775807 // long
9007199254740991 // js
3. 解决方案
对于需要使用超大整数的场景,服务端一律使用 String
字符串类型返回,禁止使用Long
类型。
4. 案例演示
测试接口如下:
@RestController
public class UserController {
@RequestMapping("/test")
public UserVO test() {
UserVO userVO = new UserVO();
userVO.setId(1699657986705854464L);
userVO.setUsername("jack");
userVO.setBirthday(new Date());
List<String> roleList = new ArrayList<>();
roleList.add("管理员");
roleList.add("经理");
userVO.setRoleList(roleList);
return userVO;
}
}
4.1 方式一:使用注解
针对Long
类型的属性,使用@JsonSerialize
注解指定序列化器为ToStringSerializer
:
@JsonSerialize(using = ToStringSerializer.class)
Long id;
输出结果:
{
"id": "1699657986705854464",
"username": "jack",
"roleList": "管理员,经理",
"birthday": "2024-04-16 14:40:39"
}
4.2 方式二:全局配置
上面的配置只能作用于被注解标识的字段,实际的需求需要作用有所有Long
类型,所以需要全局配置。
4.2.1 方案分析
首先默认情况下,Long
类型的数据是调用的LongSerializer
进行序列化的,需要指定Long
类型对应的序列化器为ToStringSerializer
。
@JacksonStdImpl
public static class LongSerializer extends Base<Object> {
public LongSerializer(Class<?> cls) {
super(cls, NumberType.LONG, "integer");
}
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeNumber((Long)value);
}
}
在工厂 BeanSerializerFactory
创建序列化器的_createSerializer2
方法中,可以看到会优先查询自定义序列化器:
protected JsonSerializer<?> _createSerializer2(SerializerProvider prov,
JavaType type, BeanDescription beanDesc, boolean staticTyping)
throws JsonMappingException {
JsonSerializer<?> ser = null;
final SerializationConfig config = prov.getConfig();
// 容器类型 (LIST、MAP)
if (type.isContainerType()) {
if (!staticTyping) {
staticTyping = usesStaticTyping(config, beanDesc);
}
// 03-Aug-2012, tatu: As per [databind#40], may require POJO serializer...
ser = buildContainerSerializer(prov, type, beanDesc, staticTyping);
// Will return right away, since called method does post-processing:
if (ser != null) {
return ser;
}
} else {
// 非容器类型
if (type.isReferenceType()) {
ser = findReferenceSerializer(prov, (ReferenceType) type, beanDesc, staticTyping);
} else {
// POJO类型
// 查询模块中定义的序列化器(通过SerializerFactoryConfig 配置)
for (Serializers serializers : customSerializers()) {
ser = serializers.findSerializer(config, type, beanDesc);
if (ser != null) {
break;
}
}
}
// 查询注解中定义的序列化类型
if (ser == null) {
ser = findSerializerByAnnotations(prov, type, beanDesc);
}
}
这里的自定义序列化器是指通过SerializerFactory API
直接添加,或者通过模块注册的序列化器:
public abstract class SerializerFactory {
public abstract SerializerFactory withAdditionalSerializers(Serializers sers);
// 省略.....
}
// 注册模块方法中,实际也是调用了 SerializerFactory
public void addDeserializers(Deserializers d) {
DeserializerFactory df = ObjectMapper.this._deserializationContext._factory.withAdditionalDeserializers(d);
ObjectMapper.this._deserializationContext = ObjectMapper.this._deserializationContext.with(df);
}
例如,对jsr310
支持的模块JavaTimeModule
中,时间类型对应的序列化器都添加进来了:
总结:我们只需要通过SerializerFactory API
或者通过模块注册,指定Long
类型对应的序列化器为ToStringSerializer
4.2.2 配置
1、SerializerFactory
直接在注册ObjectMapper
时,通过SerializerFactory
配置:
@Bean
@Primary
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
SimpleSerializers sers = new SimpleSerializers();
sers.addSerializer(Long.class, ToStringSerializer.instance); // 包装类型:Long
sers.addSerializer(Long.TYPE, ToStringSerializer.instance); // 基本类型:long
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SerializerFactory serializerFactory = objectMapper
.getSerializerFactory()
.withAdditionalSerializers(sers)
.withSerializerModifier(new NullValueBeanSerializerModifier());
objectMapper.setSerializerFactory(serializerFactory);
return objectMapper;
}
测试结果如下:
{
"id": "1699657986705854464",
"username": "jack",
"roleList": "管理员,经理",
"birthday": "2024-04-16 16:03:22"
}
2、Jackson2ObjectMapperBuilder
Jackson2ObjectMapperBuilder
中声明了添加序列化器的相关方法:
public Jackson2ObjectMapperBuilder serializerByType(Class<?> type, JsonSerializer<?> serializer) {
this.serializers.put(type, serializer);
return this;
}
public Jackson2ObjectMapperBuilder serializersByType(Map<Class<?>, JsonSerializer<?>> serializers) {
this.serializers.putAll(serializers);
return this;
}
这些添加的序列化器的会在configure
方法中,通过模块注册到ObjectMapper
中:
if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
SimpleModule module = new SimpleModule();
this.addSerializers(module);
this.addDeserializers(module);
objectMapper.registerModule(module);
}
使用Jackson2ObjectMapperBuilder
构建对象时配置ToStringSerializer
:
@Bean
@Primary
ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder
.serializerByType(Long.class, ToStringSerializer.instance)
.serializerByType(Long.TYPE, ToStringSerializer.instance)
.createXmlMapper(false).build();
SerializerFactory serializerFactory = objectMapper
.getSerializerFactory()
.withSerializerModifier(new NullValueBeanSerializerModifier());
objectMapper.setSerializerFactory(serializerFactory);
return objectMapper;
}
3、Jackson2ObjectMapperBuilderCustomizer
使用Jackson2ObjectMapperBuilderCustomizer
进行定制,这种方式更加简洁明了。
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.serializerByType(Long.class, ToStringSerializer.instance);
builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
};
}