java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot解决Long精度丢失与日期格式化

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

作者:Mr.4567

在前后端分离的开发模式中,JSON序列化与反序列化是最常见的数据交互方式,SpringBoot默认使用Jackson作为JSON处理库,本文将通过一个完整的JacksonConfig配置类,一次性解决Long精度丢失与日期格式化问题,并附带详细的原理分析与踩坑指南,需要的朋友可以参考下

前言

在前后端分离的开发模式中,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 类型字段都转为字符串,包括 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")));

为什么必须同时配置序列化和反序列化?

如果只配序列化不配反序列化,前端传 "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 的构造函数参数含义:

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 可以让前端明确知道哪些字段存在但值为空,便于调试。如果你的业务更希望减小响应体积,可以改为 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精度丢失与日期格式化的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文