有道无术,术尚可求,有术无道,止于术。
本系列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 // js9223372036854775807 // 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
。
@JacksonStdImplpublic 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@PrimaryObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {SimpleSerializers sers = new SimpleSerializers();sers.addSerializer(Long.class, ToStringSerializer.instance); // 包装类型:Longsers.addSerializer(Long.TYPE, ToStringSerializer.instance); // 基本类型:longObjectMapper 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@PrimaryObjectMapper 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
进行定制,这种方式更加简洁明了。
@Beanpublic Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {return builder -> {builder.serializerByType(Long.class, ToStringSerializer.instance);builder.serializerByType(Long.TYPE, ToStringSerializer.instance);};}