这是为了解决 JavaScript 处理大数字精度问题,我们可以通过以下方式实现:
- 创建 Jackson 配置类:
// src/main/java/top/roozen/lab/competition/config/JacksonConfig.java
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 注册Long类型序列化器
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
// 其他常用配置
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
- 如果使用 Spring MVC,还可以配置全局消息转换器:
// src/main/java/top/roozen/lab/competition/config/WebMvcConfig.java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(0, jackson2HttpMessageConverter);
}
}
这样配置后:
- 所有Long类型的值都会以字符串形式返回
- 避免了JavaScript处理大数字时的精度丢失问题
- 同时配置了一些常用的Jackson序列化选项
注意事项:
- 这个配置会影响全局的JSON序列化
- 如果某些特殊场景不需要转换为字符串,可以使用
@JsonSerialize
注解单独配置 - 建议在项目初期就进行此配置,避免后期出现精度问题
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
这两句的区别在于处理的是不同的 Long 类型:
Long.class
处理的是包装类型Long
Long.TYPE
处理的是基本类型long
举例说明:
public class Example {
private Long wrappedLong = 123L; // 包装类型 Long
private long primitiveLong = 456L; // 基本类型 long
}
如果只配置其中一个:
// 只配置 Long.class
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
// 结果
{
"wrappedLong": "123", // 会转换为字符串
"primitiveLong": 456 // 仍然是数字
}
// 只配置 Long.TYPE
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
// 结果
{
"wrappedLong": 123, // 仍然是数字
"primitiveLong": "456" // 会转换为字符串
}
所以需要同时配置这两句,才能确保所有的 Long 类型(无论是包装类型还是基本类型)都能被正确地序列化为字符串:
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
// 结果
{
"wrappedLong": "123", // 转换为字符串
"primitiveLong": "456" // 转换为字符串
}
这种情况在 Java 中很常见,因为:
- 类的属性通常使用包装类型(
Long
) - 基本类型(
long
)通常用在方法参数、局部变量等场景 - 有些框架(如 MyBatis)会自动在包装类型和基本类型之间转换
所以为了确保所有场景下的 Long 值都能正确序列化为字符串,需要同时配置这两种类型。
- 如果只配置
ObjectMapper
Bean:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}
这种配置足够处理 @RestController
返回的对象,因为:
- Spring MVC 默认使用
MappingJackson2HttpMessageConverter
- 这个转换器会自动使用容器中的
ObjectMapper
Bean - 所以不需要额外配置
WebMvcConfigurer
- 什么时候需要配置全局消息转换器:
- 当你需要自定义多个
HttpMessageConverter
的顺序时 - 当你需要添加其他自定义的消息转换器时
- 当你需要完全控制消息转换的过程时
例如:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加自定义的消息转换器并设置优先级
converters.add(0, new CustomConverter());
// 配置Jackson转换器
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
// ... 配置 ObjectMapper
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(1, jackson2HttpMessageConverter);
}
}
- 实际建议:
// src/main/java/top/roozen/lab/competition/config/JacksonConfig.java
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 配置Long转字符串
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
// 其他常用配置
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
return objectMapper;
}
}
这个配置就足够了,因为:
- 能满足
@RestController
的需求 - 配置简单清晰
- 其他需要使用
ObjectMapper
的地方也能复用这个配置
除非你有特殊需求(如上面提到的自定义转换器),否则不需要配置 WebMvcConfigurer
。
示例测试:
@RestController
@RequestMapping("/api/test")
public class TestController {
@GetMapping("/long")
public Map<String, Long> testLong() {
Map<String, Long> result = new HashMap<>();
result.put("wrappedLong", 123456789012345L);
result.put("primitiveLong", 987654321098765L);
return result;
}
}
响应结果:
{
"wrappedLong": "123456789012345",
"primitiveLong": "987654321098765"
}
这证明只配置 ObjectMapper
Bean 就能正确处理 Long 类型的序列化。
评论区