大家好,我是G探险者!
在使用 Jackson 进行 JSON 序列化和反序列化时,我们有时会遇到数字被以 科学计数法(Scientific Notation) 形式表示的问题。这种表现形式在大多数计算场景下无伤大雅,但在涉及精度敏感、报文协议或前端展示时,往往会带来不小的困扰。
本文将详细探讨:
- ✅ 什么是科学计数法?
- ✅ Jackson 在什么情况下会触发科学计数法?
- ✅ 如何通过配置规避 Jackson 中的科学计数法?
- ✅ 常见的坑与对策
📌 什么是科学计数法?
科学计数法是一种用指数形式表达非常大或非常小的数字的方法。其形式通常为:
1.23E5 => 1.23 × 10^5 => 123000
Java 中的 double 和 float 类型在打印或序列化时,默认就可能使用科学计数法。
🎯 Jackson 触发科学计数法的典型条件
在使用 Jackson 处理数字时,是否以科学计数法表示,取决于以下几方面:
✅ 1. 数据类型决定表现形式
| 数据类型 | 是否易触发科学计数法 | 说明 |
|---|---|---|
double / Double | ✅ 高 | Java 默认使用科学计数法表示极大或极小的值 |
float / Float | ✅ 高 | 精度更低,更容易触发科学计数法 |
BigDecimal | ❌ 低 | 精度高,Jackson 默认不会使用科学计数法(除非关闭某些配置) |
✅ 2. 数值大小触发阈值(以 double 为例)
| 数值范围 | 是否可能触发科学计数法 |
|---|---|
< 0.001(1E-3 以下) | ✅ 可能,例如 0.0000001 → 1E-7 |
>= 10000000(1E7 以上) | ✅ 可能,例如 12345678.9 → 1.23456789E7 |
| 正常整数、小数(两位数) | ❌ 不会,例如 123.45、12.34 |
这是一种行为优化,Jackson 会尝试在 JSON 序列化时生成更短的字符串。
✅ 3. 示例:Jackson 默认行为
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(12345.67));
// 输出: 12345.67 ✅
System.out.println(mapper.writeValueAsString(12345678.9));
// 输出: 1.23456789E7 ❌
System.out.println(mapper.writeValueAsString(0.000123));
// 输出: 1.23E-4 ❌
🧪 BigDecimal 是如何处理的?
BigDecimal 是精度友好的数值类型,Jackson 默认不会用科学计数法。但当关闭了相关配置或位数特别长时,也有可能被转为科学计数法。
✅ 示例
BigDecimal value = new BigDecimal("1234567890123456789012345678901234");
System.out.println(mapper.writeValueAsString(value));
// 默认输出:1234567890123456789012345678901234 ✅
❌ 关闭配置后的行为
mapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, false);
可能导致输出为:
1.2345678901234568E33 ❌
🛠 如何规避 Jackson 的科学计数法?
✅ 方式 1:反序列化时启用 BigDecimal
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
避免将浮点数反序列化为 double,从而防止精度丢失和科学计数法。
✅ 方式 2:序列化时强制以普通字符串输出 BigDecimal
mapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, true);
确保 Jackson 在序列化时调用 BigDecimal.toPlainString() 而不是 toEngineeringString() 或 toString()。
✅ 方式 3:统一使用字符串表示数值(如账务金额)
在精度极其重要的场景中,如金融、电商等,可以直接用字符串保存数值:
@JsonSerialize(using = ToStringSerializer.class)
private BigDecimal amount;
🚧 实战坑点总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
前端拿到 1.23E8 无法解析 | Jackson 默认输出科学计数法 | 配置 WRITE_BIGDECIMAL_AS_PLAIN |
| 精度丢失,金额计算出错 | double 存储金额 | 使用 BigDecimal |
| 科学计数法序列化后值变长 | BigDecimal.toString() 被默认调用 | 强制使用 .toPlainString() |
✅ 结语
Jackson 并不会“自动触发”科学计数法,而是在数值超出正常范围后,出于效率和表达优化的考虑,采用了科学记数的格式。开发者应在:
- 💰 金融业务;
- 🧮 精度敏感场景;
- 📤 前后端数值交互场景;
中对科学计数法的触发条件与规避方式有足够了解,才能保障系统的稳定性和准确性。