Jackson[1] 是我們編寫 Java 應用時常用的 JSON 序列化/反序列化庫。今天說說使用它的另一個技巧:為特定欄位編寫自己的序列化方法(或者,在序列化前,怎麼先格式化特定欄位)。
之前介紹過的 Jackson 的技巧:巧用 @JsonRawValue 註解。
假設某 API 返回的 JSON 結構如下:
{
"a": 10000,
"b": 12345
}前端拿到該數據後,要做如下渲染:
即,數字要轉化為「萬」,且保留一位小數。
關於數值格式化,通常的做法是以下 2 種:
後端返回原始數值,前端拿到數值後,先格式化再顯示。
缺點:前端需要寫格式化邏輯;當前端有多端時(web / ios / android),每一端都要寫自己的格式化函數。後端直接返回格式化後的欄位,前端拿到數據後直接顯示。
優點:前端只需要負責顯示,無需額外邏輯;當涉及多平臺時,省了前端重複的工作量。缺點:後端不能偷懶了;當這些數字還需要在前端參與數學運算時,只返回格式化的文本是不夠的。本文說說採用2,讓後端直接返回格式化後的欄位,有沒有好的實現方式。
2 的返回格式如下:
{"a":"1w","b":"1.2w"}我們先討論一下兩種可能的實現方法。
假設有 Model 和 Dto 類如下:
@Data
class Model {
int a;
int b;
}
@Data
class Dto {
String a;
String b;
}另外有一個格式化數值的方法:
// 格式化數值
String format(int num){
// 省略
}
方式一:手動轉換即 一個欄位一個欄位的拷貝,拷貝前先轉換一下:
// 手動調用格式化方法
dto.setA(format(model.getA()));
dto.setB(format(model.getB()));這種方式雖然能達到目的,但每個需要格式化的欄位都需要手動調用 format 方法,就問你煩不煩?
而且,像這類從一個 bean 轉換到另一個 bean 的操作,由於兩邊欄位類型不同,也不能方便地使用 MapStruct 等工具自動轉換,是不是更煩了?
所以我們引入方法二。
方法二:利用註解,聲明式配置其實,Jackson 允許我們在序列化對象時,指定某個欄位的序列化方式,如下面這樣:
先改寫 Dto 類,保持欄位類型和 Model 一致,然後給各個欄位加一個註解配置:
@Data
class Dto {
@JsonSerialize(converter = MyConverter.class)
int a;
@JsonSerialize(converter = MyConverter.class)
int b;
}其中,@JsonSerialize 註解用來進行個性化配置,converter 參數的作用,是在序列化指定欄位之前,用指定轉換器先進行轉換操作,Jackson 再對轉換後的結果進行序列化。
MyConverter 類的實現如下:
import com.fasterxml.jackson.databind.util.StdConverter;
class MyConverter
extends StdConverter<Integer, String> {
@Override
public String convert(Integer num) {
// 格式化數值
return format(num);
}
}如此,該 Dto 中的 a、b 欄位,在序列化之前,會先被格式化,即:
對於
{
"a": 10000,
"b": 12345
}會自動轉換成
{
"a": "1w",
"b": "1.2w"
}利用註解配置的好處是:
當某天需求變動,不需要返回格式化的文本或格式化算法有調整時,可以通過修改 MyConverter 類來統一更改。避免了每個欄位都要去手動調用 format 的繁瑣。由於 Model 和 Dto 欄位名和類型一樣,方便使用 MapStruct 等工具自動生成轉換類。 總結文章介紹了 Jackson 工具庫在序列化 Java Bean 時,利用 @JsonSerialize 註解的 converter 配置項,可以輕鬆定製欄位的序列化方式。該註解還有別的屬性可配置,建議讀者查看文檔進一步學習。
參考資料[1]FasterXML/jackson: https://github.com/FasterXML/jackson
- END -
轉發支持作者🍻