当前位置: 代码网 > it编程>编程语言>Java > SpringBoot配置Jackson解决Long精度丢失与日期格式化问题的实战指南

SpringBoot配置Jackson解决Long精度丢失与日期格式化问题的实战指南

2026年09月28日 • Java •我要评论
前言在前后端分离的开发模式中,json 序列化与反序列化是最常见的数据交互方式。spring boot 默认使用 jackson 作为 json 处理库,但在实际项目中,我们经常会遇到以下两个&quo

前言

在前后端分离的开发模式中,json 序列化与反序列化是最常见的数据交互方式。spring boot 默认使用 jackson 作为 json 处理库,但在实际项目中,我们经常会遇到以下两个"老大难"问题:

  1. 前端 js 处理大 long 类型数字精度丢失:java 的 long 最大值是 9223372036854775807,而 javascript 的 number 类型最大安全整数是 2^53 - 1(即 9007199254740991)。当后端返回雪花算法 id、订单号等超过此范围的数字时,前端接收到的值会被"篡改"。
  2. 日期时间格式化不统一:默认返回的时间戳或 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(),需要确保:

  1. 加 @primary 注解,让它覆盖默认的 objectmapper。
  2. 或者使用 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" 是一个完整的闭环方案。

五、生产环境建议

  1. 统一响应规范:结合 @jsonformat 注解可以在字段级别覆盖全局配置,处理特殊日期格式需求。
  2. 单元测试覆盖:编写测试用例,验证 long、localdatetime、date 的序列化和反序列化结果。
  3. 前端约定:团队需明确 long 类型统一按字符串处理,避免前端直接用算术运算。
  4. dto 分离:对外接口尽量使用 dto,避免直接暴露实体类,方便控制序列化行为。
  5. 不要重复配置:如果同时使用 application.yml 中的 spring.jackson.* 配置和此 objectmapper bean,可能产生冲突,建议二选一。

六、验证效果

配置完成后,启动项目,访问接口,返回结果如下:

修改前:

{
  "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精度丢失与日期格式化的资料请关注代码网其它相关文章!

赞 (0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com