You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot反序列化BigDecimal时丢失末尾小数零如何解决

问题背景

我有一个Spring Boot应用,用于消费某REST API返回的小数值,接口返回示例如下:

"Amount": {
  "Amount": 160.10,
  "Currency": "EUR"
}

将金额字段映射为BigDecimal类型时遇到问题:需要保留数值末尾的小数位(即使该位为0),因为业务需要调用BigDecimal.unscaledValue()获取去除小数点后的无小数整数值。

对于上述示例数据,预期x.getAmount().unscaledValue()的返回结果为"16010",但由于框架内部将值解析为160.1(无意义的末尾0被自动剔除),实际返回结果为"1601",导致下游除以10的计算结果偏差10倍。
根据对应币种的不同,接口返回的金额值可能有0位、2位或3位小数。
最初尝试使用setScale()方法处理该问题,但未取得预期效果。

备注:无法修改上游API将数值改为字符串格式传输。

相关代码

Java 11 解析JSON代码

restTemplate 
  .exchange(request, new ParameterizedTypeReference<List<NotificationMessage>>() {})
  .getBody()

实体模型代码

import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import java.math.BigDecimal;

import lombok.Data;

@Data
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
public class TransactionAmount {
//  @JsonRawValue // 测试用,未生效
    private BigDecimal amount;

    private String currency;

}

需求:找到方案强制Spring Boot在反序列化阶段保留数值末尾无意义的小数零。


解决方案

问题根源是Jackson默认的BigDecimal反序列化逻辑会将数值转为最简表示,自动丢弃末尾的0,丢失原始小数位信息。由于不同币种小数位数不固定,不能硬编码setScale()固定小数位,正确做法是自定义反序列化器,直接读取JSON原始的数字文本构造BigDecimal,完整保留原始的scale值。

步骤1:编写自定义BigDecimal反序列化器

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.math.BigDecimal;

public class RawBigDecimalDeserializer extends JsonDeserializer<BigDecimal> {
    @Override
    public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        // 直接读取JSON原始数字文本构造BigDecimal,不做数值化简
        return new BigDecimal(p.getText());
    }
}

步骤2:绑定反序列化器

单字段生效方案

修改TransactionAmount类的amount字段,添加@JsonDeserialize注解绑定自定义反序列化器即可:

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
// 其余导入保持不变

@Data
@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
public class TransactionAmount {
    @JsonDeserialize(using = RawBigDecimalDeserializer.class)
    private BigDecimal amount;

    private String currency;
}

全局生效方案

如果项目中所有BigDecimal字段都需要保留原始小数位,可以直接将自定义反序列化器注册到RestTemplate使用的ObjectMapper中,不需要逐个字段加注解:

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    // 替换Jackson消息转换器的ObjectMapper配置
    for (HttpMessageConverter<?> converter : restTemplate.getMessageConverters()) {
        if (converter instanceof MappingJackson2HttpMessageConverter jacksonConverter) {
            ObjectMapper objectMapper = jacksonConverter.getObjectMapper();
            SimpleModule bigDecimalModule = new SimpleModule();
            bigDecimalModule.addDeserializer(BigDecimal.class, new RawBigDecimalDeserializer());
            objectMapper.registerModule(bigDecimalModule);
            break;
        }
    }
    return restTemplate;
}

效果验证

针对示例中的160.10,反序列化得到的BigDecimal实例scale为2,调用unscaledValue()会返回16010,完全符合业务预期;如果上游传3位小数比如160.100,得到的scale为3,unscaledValue()返回160100,不需要针对不同币种做特殊适配。

注意:@JsonRawValue是序列化阶段生效的注解,对反序列化逻辑没有任何作用,不要用它尝试解决该问题。


内容的提问来源于stack exchange,提问作者JoSSte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 04:36:14