前言
在前后端分离的开发模式中,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精度丢失与日期格式化的资料请关注代码网其它相关文章!
发表评论