Spring Boot 将 JSON 中的 Long 值序列化为 String 避免精度丢失

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
整形ID转字符串
@JsonSerialize(using = ToStringSerializer.class)

什么是精度丢失?

Java 中长整形 Long (64位)的取值范围是:-9223372036854775808 - 9223372036854775807

在这种情况下,由于 JavaScript 的 Number 类型是 64 位浮点数,它无法精确表示超过 53 位的整数。因此,当将 Java Long 类型的值传递给 JavaScript 时,可能会发生精度丢失。

你可以在浏览器控制台运行如下代码,更直观地感受 “精度丢失” 的问题。

let val = 9223372036854775807;
console.log(val); //9223372036854776000 输出的值丢失了精度

Jackson 的注解支持

Spring Boot 默认使用 Jackson 作为 JSON 的序列化、反序列化框架。Jackson 提供了 @JsonSerialize 注解,该注解的 using 属性可以指定一个 JsonSerializer 的实现类,用于自定义字段的序列化方式。

Jackson 已经预定义了一个实现 ToStringSerializer,用于把指定的字段序列化为字符串。

定义一个简单的 User 对象:

package cn.springdoc.demo.model;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

public class User {

    // 把 Long 类型的 id 序列化为 字符串
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    private String name;

    // 忽略 get / set 方法
}

定义一个 Controller 进行测试:

package cn.springdoc.demo.web.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.springdoc.demo.model.User;


@RestController
@RequestMapping("/demo")
public class DemoController {

    @PostMapping
    public User demo (@RequestBody User user) {
        return user; 
    }
}

如上,定义一个简单的端点,接收客户端 POST 的 JSON 字符串,封装为 User 对象后原样返回。

启动应用,使用 curl 进行测试:

$ curl -X POST -H "Content-Type: application/json" -d '{"id": 9223372036854775807, "name": "springdoc.cn"}' http://localhost:8080/demo

{"id":"9223372036854775807","name":"springdoc.cn"}

如你所见,请求体中的 id 字段是一个数值类型,其值为 9223372036854775807,也就是 Long 的最大值。Jackson 准确地把请求体封装为了 User 对象。

并且在响应 JSON 中,id 字段类型是 String,说明注解生效!

id 字段如果以字符串形式传递,Jackson 也能自动解析为 Long 类型:

$ curl -X POST -H "Content-Type: application/json" -d '{"id": "9223372036854775807", "name": "springdoc.cn"}' http://localhost:8080/demo

{"id":"9223372036854775807","name":"springdoc.cn"}

全局设置

@JsonSerialize 注解的弊端在于,需要对所有 Long 类型的字段进行一一设置,这太麻烦。

通过 Spring Boot 的 Jackson2ObjectMapperBuilderCustomizer 配置类,可以对 Jackson 进自定义,从而指定所有 Long 类型的序列化方式为 String

package cn.springdoc.demo.configuration;

import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

@Configuration
public class JacksonConfiguration {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            // 把 Long 类型序列化为 String
            builder.serializerByType(Long.class, ToStringSerializer.instance);
        };
    }
}

删除 User 对象中的注解:

package cn.springdoc.demo.model;

public class User {

    private Long id;
    private String name;
    // 省略 get / set 方法
}

重启应用,再次使用 curl 进行测试:

$ curl -X POST -H "Content-Type: application/json" -d '{"id": "9223372036854775807", "name": "springdoc.cn"}' http://localhost:8080/demo

{"id":"9223372036854775807","name":"springdoc.cn"}

 注:也可以直接使用该组件替换如上内容

import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.math.BigInteger;

@Configuration
public class JacksonConfig {
    /**
     * Jackson全局转化long类型为String,解决jackson序列化时传入前端Long类型缺失精度问题
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> builder
                .serializerByType(BigInteger.class, ToStringSerializer.instance)
                .serializerByType(Long.class, ToStringSerializer.instance);
    }
}
posted @ 2024-05-04 13:08  createMan  阅读(62)  评论(0编辑  收藏  举报