- 核心解決方案
通過 自定義序列化器 + @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)),在序列化器內解析。
保存此筆記,隨時查閱! ?