别再让BigDecimal坑你了!Spring Boot项目里Jackson和Fastjson的完整避坑配置
从科学计数法到清晰展示:Spring Boot中BigDecimal的终极处理方案
那天凌晨三点,我被紧急电话叫醒——生产环境的订单系统突然显示所有金额都变成了"1.23E+4"这样的格式。财务部门已经炸开了锅,因为没人能看懂这些科学计数法表示的具体金额。这就是我第一次深刻认识到BigDecimal序列化问题的严重性。作为Java开发者,我们选择BigDecimal就是为了精确表示金融数据,但如果不处理好序列化问题,反而会造成更大的混乱。
1. 问题本质:为什么BigDecimal会变成科学计数法?
BigDecimal被设计为可以精确表示任意精度的十进制数,但当我们把它放入Web应用的JSON响应中时,经常会遇到两个层面的问题:
- 后端序列化问题 :默认情况下,Jackson和Fastjson都会将BigDecimal按照数值类型处理,当数值过大或过小时,会自动转换为科学计数法表示
- 前端解析问题 :即使后端正确输出了完整数字,JavaScript在解析JSON时也可能自动将长数字转换为科学计数法
关键区别 :后端序列化问题会导致响应体本身就包含科学计数法,而前端问题则是响应体正常但显示异常。要确认问题来源,最简单的方法是用Postman等工具直接查看原始响应:
// 问题出在后端的情况
{
"amount": 1.23E+4
}
// 问题出在前端的情况
{
"amount": 12300.00
}
2. Fastjson解决方案:全局与局部的精准控制
2.1 全局配置:一劳永逸的方案
对于新项目或能够接受全面改造的系统,全局配置是最稳妥的选择。Fastjson提供了 SerializeConfig 来实现类型级别的序列化控制:
public class BigDecimalSerializer implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object,
Object fieldName, Type fieldType, int features) throws IOException {
if (object == null) {
serializer.out.writeNull();
return;
}
BigDecimal value = (BigDecimal) object;
// 去除末尾多余的0并转换为普通字符串形式
serializer.write(value.stripTrailingZeros().toPlainString());
}
}
@Configuration
public class FastjsonConfig {
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
// 关键配置:注册全局的BigDecimal序列化器
config.setSerializeConfig(new SerializeConfig()
.put(BigDecimal.class, new BigDecimalSerializer()));
converter.setFastJsonConfig(config);
return new HttpMessageConverters(converter);
}
}
注意事项 :
- 此配置会影响所有BigDecimal字段,确保它们都以字符串形式输出
stripTrailingZeros()会去掉不必要的末尾零(如100.00变为100)- 对于金融系统,可能需要保留两位小数,可以在序列化器中添加格式化逻辑
2.2 注解驱动:细粒度的字段控制
在遗留系统中,可能只需要对特定字段进行处理。Fastjson提供了 @JSONField 注解来实现字段级别的控制:
public class OrderDTO {
// 其他字段保持默认序列化方式
@JSONField(serializeUsing = BigDecimalSerializer.class)
private BigDecimal totalAmount;
// 标准getter/setter
}
适用场景 :
- 系统大部分BigDecimal字段不需要特殊处理
- 只有少数金融相关字段需要确保精确表示
- 渐进式改造过程中的临时方案
3. Jackson解决方案:Spring Boot默认引擎的调优
3.1 全局配置:定制ObjectMapper
Spring Boot默认使用Jackson,我们可以通过配置 ObjectMapper 来改变其行为:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
// 添加自定义序列化器
module.addSerializer(BigDecimal.class, new JsonSerializer<BigDecimal>() {
@Override
public void serialize(BigDecimal value, JsonGenerator gen,
SerializerProvider provider) throws IOException {
// 保留两位小数并转为字符串
gen.writeString(value.setScale(2, RoundingMode.HALF_UP).toPlainString());
}
});
// 添加自定义反序列化器
module.addDeserializer(BigDecimal.class, new JsonDeserializer<BigDecimal>() {
@Override
public BigDecimal deserialize(JsonParser p,
DeserializationContext ctxt) throws IOException {
return new BigDecimal(p.getValueAsString());
}
});
mapper.registerModule(module);
return mapper;
}
}
版本适配建议 :
- Spring Boot 1.x: 使用
Jackson2ObjectMapperBuilder - Spring Boot 2.x: 直接配置
ObjectMapper更灵活 - 对于Spring Boot 2.3+,可以考虑使用
Jackson2ObjectMapperBuilderCustomizer
3.2 注解方案:最小化影响
与Fastjson类似,Jackson也提供了字段级别的控制:
public class ProductDTO {
@JsonSerialize(using = BigDecimalToStringSerializer.class)
@JsonDeserialize(using = StringToBigDecimalDeserializer.class)
private BigDecimal price;
// 其他字段
}
最佳实践 :
- 在DTO类上使用注解,而不是直接修改实体类
- 为序列化和反序列化分别配置,确保双向转换的正确性
- 考虑创建自定义组合注解简化重复配置
4. 前端协作:确保全链路一致性
即使后端处理得当,前端仍可能遇到数值显示问题。完整的解决方案应该包含前后端约定:
-
数据类型约定 :
- 方案A:所有金额字段统一为字符串类型
- 方案B:在JSON Schema中明确字段格式要求
-
前端处理示例 :
// 使用专门的金额显示组件
<MoneyDisplay :value="order.totalAmount" precision="2"/>
// 或者全局过滤器
Vue.filter('money', value => {
if (typeof value === 'string') return value;
return new Big(value).toFixed(2);
});
- 联调检查清单 :
- 确认HTTP响应头
Content-Type: application/json - 验证响应体数据格式是否符合约定
- 检查前端是否有可能覆盖响应数据的拦截器
- 确认HTTP响应头
5. 深入原理:序列化过程中的关键决策点
理解底层机制有助于做出更合理的架构决策:
Jackson处理流程 :
- 检测字段类型(通过反射或显式类型信息)
- 查找注册的
JsonSerializer(按照类型→注解的顺序) - 调用
serialize()方法生成JSON内容
Fastjson处理差异 :
- 使用
SerializeConfig查找序列化器 - 支持更多的内置特性(如
SerializerFeature) - 线程安全的全局配置与实例级别配置
性能考量 :
| 方案 | 优点 | 缺点 |
|---|---|---|
| 全局配置 | 一劳永逸,一致性高 | 可能影响不需要处理的字段 |
| 注解配置 | 精准控制,影响面小 | 需要显式标记每个字段 |
| 前端转换 | 不依赖后端实现 | 增加前端复杂度 |
在微服务架构中,建议在API网关层或基础库中统一处理这类跨服务问题,而不是让每个服务自行实现。
更多推荐

所有评论(0)