- 核心解决方案
通过 自定义序列化器 + @JsonSerialize 注解,实现 BigDecimal 到百分比字符串的自动转换。
1.1 自定义序列化器代码
java
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
/**
-
将 BigDecimal 序列化为百分比字符串(如 0.85 → “85.00%”)
*/
public class BigDecimalPercentSerializer extends JsonSerializer {
private static final DecimalFormat PERCENT_FORMAT;static {
PERCENT_FORMAT = new DecimalFormat(“0.00%”); // 格式化为 2 位小数
PERCENT_FORMAT.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.CHINA)); // 中文环境
}@Override
public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null) {
gen.writeNull(); // 处理 null 值
} else {
gen.writeString(PERCENT_FORMAT.format(value)); // 输出带 % 的字符串
}
}
}
1.2 在 DTO 字段上使用注解
java
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
@Data
public class MyDTO {
@JsonSerialize(using = BigDecimalPercentSerializer.class)
private BigDecimal completionRate; // 0.85 → “85.00%”
}
-
关键点解析
2.1 为什么不用 @JsonFormat?
● @JsonFormat(pattern = “0.00%”) 不生效,因为它仅支持基本数字格式化,无法自动添加 % 符号。
● 自定义序列化器可以完全控制输出格式。
2.2 线程安全性
● DecimalFormat 非线程安全,但通过 static 初始化 + 局部使用,可避免并发问题。
如果项目要求严格线程安全,可用 ThreadLocal 包装:
● java
private static final ThreadLocal PERCENT_FORMAT = ThreadLocal.withInitial(() -> {
DecimalFormat df = new DecimalFormat(“0.00%”);
df.setDecimalFormatSymbols(DecimalFormatSymbols.getInstance(Locale.CHINA));
return df;
});
2.3 支持多语言环境
● 通过 Locale.CHINA 确保小数点、百分号符合中文习惯(如 85.00% 而非 85,00%)。
● 如果需要国际化,可从请求头获取 Locale 动态调整(需额外逻辑)。 -
进阶优化
3.1 封装自定义注解(可选)
若多处使用,可定义 @PercentFormat 注解简化代码:
java
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = BigDecimalPercentSerializer.class)
public @interface PercentFormat {}
使用方式:
java
@Data
public class MyDTO {
@PercentFormat // 替代 @JsonSerialize(using = …)
private BigDecimal completionRate;
}
3.2 处理特殊情况
● 科学计数法:如果 BigDecimal 值可能极小(如 1E-4),需在序列化器中增加处理逻辑。
● 自定义小数位数:通过注解参数动态指定格式(如 @PercentFormat(scale = 1))。
- 其他相关技巧
4.1 反序列化(百分比字符串 → BigDecimal)
若需要从 “85%” 转回 BigDecimal,可自定义 JsonDeserializer:
java
public class PercentToBigDecimalDeserializer extends JsonDeserializer {
@Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String text = p.getText().replace(“%”, “”);
return new BigDecimal(text).divide(BigDecimal.valueOf(100));
}
}
// 在 DTO 中使用
public class MyDTO {
@JsonDeserialize(using = PercentToBigDecimalDeserializer.class)
private BigDecimal completionRate;
}
4.2 结合 MapStruct 使用
如果项目用了 MapStruct 做对象映射,可在接口中声明格式转换:
java
@Mapper
public interface MyMapper {
@Mapping(target = “completionRate”, qualifiedByName = “toPercent”)
MyDTO toDto(MyEntity entity);
@Named("toPercent")
static String toPercent(BigDecimal value) {return new DecimalFormat("0.00%").format(value);
}
}
- 总结
方案 优点 适用场景
@JsonSerialize 纯注解、无需改业务代码 简单字段级格式化
自定义注解 @PercentFormat 代码更简洁,团队统一风格 项目中有大量百分比字段
反序列化支持 完整双向转换 需要接收前端百分比字符串的场景
推荐选择:
● 优先用 @JsonSerialize,够用且灵活。
● 大量同类字段时,升级为自定义注解。
附:常见问题
❓ Q: 能直接用 @JsonFormat 吗?
→ 不能,它的 pattern 不支持百分比符号自动转换。
❓ Q: 线程安全如何保证?
→ 用 static final 或 ThreadLocal 包装 DecimalFormat。
❓ Q: 如何动态调整小数位数?
→ 通过注解参数传递(如 @PercentFormat(scale = 1)),在序列化器内解析。
保存此笔记,随时查阅! ✨