SpringBoot配置Jackson解决Long精度丢失与日期格式化问题的实战指南
作者:Mr.4567
前言
在前后端分离的开发模式中,JSON 序列化与反序列化是最常见的数据交互方式。Spring Boot 默认使用 Jackson 作为 JSON 处理库,但在实际项目中,我们经常会遇到以下两个"老大难"问题:
- 前端 JS 处理大 Long 类型数字精度丢失:Java 的
Long最大值是9223372036854775807,而 JavaScript 的Number类型最大安全整数是2^53 - 1(即9007199254740991)。当后端返回雪花算法 ID、订单号等超过此范围的数字时,前端接收到的值会被"篡改"。 - 日期时间格式化不统一:默认返回的时间戳或 ISO8601 格式,前端处理起来非常不友好。
本文将通过一个完整的 JacksonConfig 配置类,一次性解决以上问题,并附带详细的原理分析与踩坑指南。
一、问题复现
1.1 Long 精度丢失
假设后端返回一个雪花算法生成的 ID:
{
"id": 1234567890123456789
}前端接收后,console.log 打印出来的却是:
{
"id": 1234567890123456800 // 最后几位被"四舍五入"了!
}原因是 JS 在解析这个 JSON 时,把 1234567890123456789 当作 Number 处理,超出了安全整数范围,导致精度丢失。
1.2 日期格式混乱
默认情况下,LocalDateTime 会返回 ISO 格式如 2025-10-10T16:37:00,Date 可能返回时间戳,前端需要额外处理,非常繁琐。
二、完整配置类
先上代码,再逐步拆解每一个配置项的作用。
package com.cetcnav.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;
@Slf4j
@Configuration
public class JacksonConfig {
@Bean
@Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// 1. Long / BigInteger 转 String,防止前端精度丢失
SimpleModule longModule = new SimpleModule();
longModule.addSerializer(Long.class, ToStringSerializer.instance);
longModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
longModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
// 2. Java 8 时间类型序列化与反序列化配置
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(Date.class,
new DateSerializer(false, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));
mapper.registerModules(longModule, javaTimeModule);
// 3. 禁用日期转时间戳
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 4. 设置时区
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
// 5. 忽略未知属性,避免因新增字段导致反序列化失败
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 6. null 值也参与序列化
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
return mapper;
}
}三、逐项拆解
3.1 Long / BigInteger 转 String
SimpleModule longModule = new SimpleModule(); longModule.addSerializer(Long.class, ToStringSerializer.instance); longModule.addSerializer(Long.TYPE, ToStringSerializer.instance); longModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
关键点:
Long.class处理包装类型Long,Long.TYPE处理基本类型long。两者必须都注册,否则基本类型字段仍会丢失精度。BigInteger同样超出了 JS 的安全整数范围,一并转为字符串。- 使用
ToStringSerializer.instance而非自定义序列化器,性能更好。
注意:这种方式会将所有 Long 类型字段都转为字符串,包括 age、count 这类小数值。如果你的项目中小数值无需转换,可以自定义注解(如 @JsonSerialize(using = ToStringSerializer.class))在字段上单独控制。但对大多数项目来说,全局转换更省心,前端统一用 String 接收即可。
3.2 LocalDateTime 序列化与反序列化
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));为什么必须同时配置序列化和反序列化?
- 序列化:后端返回给前端时,将
LocalDateTime格式化为2025-10-10 16:37:00。 - 反序列化:前端传参给后端时,接收
2025-10-10 16:37:00格式的字符串并解析为LocalDateTime。
如果只配序列化不配反序列化,前端传 "2025-10-10 16:37:00" 会报错,因为默认的反序列化器只认 ISO 格式。
3.3 Date 类型序列化
javaTimeModule.addSerializer(Date.class,
new DateSerializer(false, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")));DateSerializer 的构造函数参数含义:
- 第一个参数
false:表示不输出时间戳(true则输出时间戳)。 - 第二个参数:
DateFormat实例。
SimpleDateFormat 是非线程安全的,虽然此处是单例序列化器内部使用,但如果你的项目中有其他并发场景,建议使用 DateTimeFormatter 或 ThreadLocal 包装。
3.4 关键开关:WRITE_DATES_AS_TIMESTAMPS
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
这个开关非常重要!
- 开启(默认):日期会以时间戳(数组或数字)形式输出。
- 关闭:日期以字符串形式输出,配合上面的自定义格式化器才能生效。
很多同学配置了 LocalDateTimeSerializer 却发现没生效,往往就是忘记关闭这个特性。
3.5 时区设置
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));统一时区为东八区,避免服务器部署在不同时区时出现时间偏差。特别提醒:如果使用 Docker 部署,容器默认时区通常是 UTC,此处必须显式指定。
3.6 忽略未知属性
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
当 JSON 中存在 Java 类没有的字段时,不抛异常。这在前后端版本不一致、字段增减频繁的场景下非常有用。
3.7 null 值处理
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
ALWAYS:所有字段都参与序列化,null也会输出。NON_NULL:null字段被忽略。
选择 ALWAYS 可以让前端明确知道哪些字段存在但值为空,便于调试。如果你的业务更希望减小响应体积,可以改为 NON_NULL。
四、常见踩坑与答疑
Q1:为什么配置了@Bean还是没生效?
答:Spring Boot 会通过 Jackson2ObjectMapperBuilder 自动构建 ObjectMapper。如果你直接 new ObjectMapper(),需要确保:
- 加
@Primary注解,让它覆盖默认的ObjectMapper。 - 或者使用
Jackson2ObjectMapperBuilderCustomizer来定制。
本文代码使用 @Primary 方式,简单直接。
Q2:@Bean方法名必须是objectMapper吗?
答:不一定,但推荐使用 objectMapper,因为 Spring Boot 的自动配置会通过方法名或类型来查找。使用这个名称可以确保覆盖默认配置。
Q3:为什么long和Long都要注册?
答:Jackson 对基本类型和包装类型使用不同的序列化器。只注册 Long.class 时,实体类中的 private long id; 字段仍然会以数字形式输出。
Q4:如何只对特定字段转 String?
答:可以使用注解方式:
public class User {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private Integer age; // 保持数字
}这样只有 id 会被转成字符串,其他字段不受影响。
Q5:前端传Long字符串时反序列化会失败吗?
答:不会。Jackson 反序列化时,如果目标是 Long 类型,接收字符串 "1234567890123456789" 也能自动转换。所以"序列化转 String、反序列化接收 String" 是一个完整的闭环方案。
五、生产环境建议
- 统一响应规范:结合
@JsonFormat注解可以在字段级别覆盖全局配置,处理特殊日期格式需求。 - 单元测试覆盖:编写测试用例,验证 Long、LocalDateTime、Date 的序列化和反序列化结果。
- 前端约定:团队需明确 Long 类型统一按字符串处理,避免前端直接用算术运算。
- DTO 分离:对外接口尽量使用 DTO,避免直接暴露实体类,方便控制序列化行为。
- 不要重复配置:如果同时使用
application.yml中的spring.jackson.*配置和此ObjectMapperBean,可能产生冲突,建议二选一。
六、验证效果
配置完成后,启动项目,访问接口,返回结果如下:
修改前:
{
"id": 1234567890123456789,
"createTime": "2025-10-10T16:37:00",
"updateDate": 1760084220000
}修改后:
{
"id": "1234567890123456789",
"createTime": "2025-10-10 16:37:00",
"updateDate": "2025-10-10 16:37:00"
}前端收到 ID 时完整无损,时间格式一目了然,完美解决了开篇提到的两大问题。
七、总结
本文通过一个 JacksonConfig 配置类,系统性解决了 Spring Boot 项目中 JSON 序列化的两大痛点:
| 问题 | 解决方案 |
|---|---|
| Long 精度丢失 | ToStringSerializer 全局转 String |
| 日期格式不统一 | JavaTimeModule + 关闭时间戳特性 |
| 时区不一致 | 显式设置 Asia/Shanghai |
| 未知字段报错 | 关闭 FAIL_ON_UNKNOWN_PROPERTIES |
掌握这些配置,可以让你的前后端交互更加稳定、规范,也能避免很多线上"莫名其妙"的 Bug。如果你的项目还没有做这些配置,赶紧动手改起来吧!
以上就是SpringBoot配置Jackson解决Long精度丢失与日期格式化问题的实战指南的详细内容,更多关于SpringBoot解决Long精度丢失与日期格式化的资料请关注脚本之家其它相关文章!
