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

