17370845950

如何在 JSON 反序列化中无损将大浮点数字符串转为 BigDecimal

json 中的高精度数字(如 `"amount": 123345555789123495.38`)若被解析为 `double` 再转 `bigdecimal`,会因 `double` 二进制精度限制导致严重失真;正确做法是**跳过 `double` 中间表示,直接从原始 json 字符串构造 `bigdecimal`**。

在 Java 处理 JSON 数据时,若使用 Jackson 等库默认将数值字段反序列化为 Double(例如通过 ConcurrentHashMap 接收),一旦 123345555789123495.38 被解析为 double,其内部已丢失精度——double 仅能精确表示约 15–17 位十进制有效数字,而该数字整数部分达 17 位,小数部分又含两位,超出 double 表示能力,实际存储值变为 1.23345555789123488E17(即 123345555789123488.00),误差达 7 元。

✅ 正确方案:绕过 double,保留原始 JSON 字符串形式。推荐两种实践方式:

方式一:使用 Jackson 的 JsonNode 显式读取为字符串

ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonString);
String amountStr = rootNode.path("amount").asText(); // 确保未被转为 number
BigDecimal amount = new BigDecimal(amountStr).setScale(2, RoundingMode.HALF_UP);

方式二:自定义反序列化器(推荐用于实体类)

public class AmountDeserializer extends JsonDeserializer {
    @Override
    public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) 
            throws IOException {
        // 强制按字符串读取,避免 double 解析
        String text = p.getText();
        return new BigDecimal(text).setScale(2, RoundingMode.HALF_UP);
    }
}

// 使用示例
public class Order {
    @JsonDeserialize(using = AmountDeserializer.class)
    private BigDecimal amount;
}

⚠️ 注意事项:

  • ❌ 避免 new BigDecimal(String.valueOf(doubleValue)):String.valueOf(1.23345555789123488E17) 仍基于已失真的 double,无法恢复原始精度;
  • ✅ 始终优先从 JsonParser.getText() 或 JsonNode.asText() 获取原始字符串;
  • ? 若业务要求严格金融精度,建议服务端统一以字符串格式传输金额字段(如 "amount": "123345555789123495.38"),并在 Schema 层面约束类型;
  • ? setScale(2, RoundingMode.HALF_UP) 更符合金融四舍五入惯例(HALF_EVEN 虽标准但易引发业务理解偏差,需与财务规范对齐)。

总结:精度丢失根源不在 BigDecimal,而在 double 这一中间环节。只要确保 JSON 数字未经 double 解析、直接以字符串进入 BigDecimal 构造器,即可实现零精度损失的高保真转换。