????????Java 生態里關于 JSON 的序列化與反序列化(以下簡稱“序列化”)是一個久經考驗的話題,卻常因框架繁多、配置瑣碎而讓初學者望而卻步。本文將圍繞一段極簡的
JsonUtils
工具類展開,以 FastJSON 與 Jackson 兩大主流實現為例,從原理到實踐、從特性到隱患,做一次系統梳理。文章力求以學術寫作之嚴謹,幫助讀者在 3000 字左右完成一次由點及面的進階。
目錄
一、為什么需要“工具類”而非直接調用框架 API
示例代碼段
二、FastJSON 實現細節與行為解讀
2.1 序列化:統一日期格式與循環引用控制
2.2 反序列化:TypeReference 的價值
2.3 異常策略:IllegalArgumentException 而非底層異常
三、Jackson 實現細節與行為解讀
3.1 ObjectMapper 的線程安全
3.2 空 Bean 與日期格式
3.3 異常處理:IOException 的簡化
四、橫向對比:FastJSON vs Jackson
五、從工具類到項目落地:一個完整的演進故事
5.1 遷移步驟
5.2 兼容性陷阱
六、再談防御式編程:邊界條件的“三重門”
七、小結與展望
一、為什么需要“工具類”而非直接調用框架 API
????????無論 FastJSON 還是 Jackson,其 API 都足夠簡潔:JSON.toJSONString(obj)
或 objectMapper.writeValueAsString(obj)
即可完成序列化。然而生產環境中,我們往往需要在“一致性”“防御式編程”“可追蹤”“可擴展”四個維度做額外約束。
- 一致性:日期格式、空值策略、循環引用檢測等行為必須全局統一。
- 防御式編程:對 null、空串、非法 JSON 的入參給出明確兜底。
- 可追蹤:異常信息須攜帶上下文(對象類型、原始 JSON 片段)。
- 可擴展:未來切換實現(如從 FastJSON 遷移到 Jackson)時業務代碼零改動。
????????因此,一個 JsonUtils
的存在絕非“重復造輪子”,而是對底層實現做“策略封裝”。下文的兩段代碼正是這一思路的極簡落地。
示例代碼段
//FastJSON
public final class JsonUtils {private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class);// ========== 構造器 ==========private JsonUtils() {}// ========== 序列化 ==========public static String toJson(Object obj) {if (obj == null) {return "null";}try {return JSON.toJSONString(obj,SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteDateUseDateFormat); // 統一日期格式} catch (Exception e) {logger.error("Serialize object to JSON failed. Object={}", obj, e);throw new IllegalArgumentException("JSON serialize error", e);}}// ========== 反序列化(單個對象) ==========public static <T> T fromJson(String json, Class<T> clazz) {if (json == null || json.isEmpty()) {return null;}try {return JSON.parseObject(json, clazz);} catch (Exception e) {logger.error("Deserialize JSON to {} failed. JSON={}", clazz.getSimpleName(), json, e);throw new IllegalArgumentException("JSON deserialize error", e);}}// ========== 反序列化(復雜泛型,如 List<User>) ==========public static <T> T fromJson(String json, TypeReference<T> typeRef) {if (json == null || json.isEmpty()) {return null;}try {return JSON.parseObject(json, typeRef);} catch (Exception e) {logger.error("Deserialize JSON to {} failed. JSON={}", typeRef.getType(), json, e);throw new IllegalArgumentException("JSON deserialize error", e);}}
}
//Jackson
public final class JsonUtils {private static final ObjectMapper MAPPER = new ObjectMapper().disable(SerializationFeature.FAIL_ON_EMPTY_BEANS).setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));private JsonUtils() {}public static String toJson(Object obj) {if (obj == null) return "null";try {return MAPPER.writeValueAsString(obj);} catch (JsonProcessingException e) {throw new IllegalArgumentException("Serialize error", e);}}public static <T> T fromJson(String json, Class<T> clazz) {if (json == null || json.isEmpty()) return null;try {return MAPPER.readValue(json, clazz);} catch (IOException e) {throw new IllegalArgumentException("Deserialize error", e);}}
}
二、FastJSON 實現細節與行為解讀
????????FastJSON 由阿里巴巴開源,以“快”著稱,實現上大量依賴 ASM 動態字節碼生成,將反射開銷降至極低。在給出的 FastJSON 版 JsonUtils
中,三條語句幾乎涵蓋日常 90% 的場景。
2.1 序列化:統一日期格式與循環引用控制
return JSON.toJSONString(obj,SerializerFeature.DisableCircularReferenceDetect,SerializerFeature.WriteDateUseDateFormat);
-
DisableCircularReferenceDetect
關閉循環引用檢測。FastJSON 默認會為循環引用生成$ref
,這在 RESTful 返回中常因前端無法解析而踩坑。關閉后,若實際出現循環引用將直接拋JSONException
,用“快速失敗”換取“數據干凈”。 -
WriteDateUseDateFormat
強制使用全局日期格式(yyyy-MM-dd HH:mm:ss
)。FastJSON 內部維護一個DateFormat
線程局部變量,因此該配置對性能幾乎無損耗。
2.2 反序列化:TypeReference 的價值
public static <T> T fromJson(String json, TypeReference<T> typeRef)
????????Java 類型擦除導致 List<User>
在運行時只剩 List
。FastJSON 的 TypeReference
借助匿名內部類保存泛型簽名,繞過擦除,反序列化時即可還原完整類型。這一點在 Jackson 中對應 TypeReference
同名類,設計思路如出一轍。
2.3 異常策略:IllegalArgumentException 而非底層異常
????????FastJSON 拋出的 JSONException
繼承自 RuntimeException
,工具類將其包裝為 IllegalArgumentException
,語義上更接近“參數非法”。這一轉換使得調用方無需顯式捕獲受檢異常,同時保持日志鏈路完整。
三、Jackson 實現細節與行為解讀
????????Jackson 是 Spring 生態的默認 JSON 方案,模塊豐富、擴展點繁多。在 JsonUtils
的 Jackson 實現中,配置集中在靜態 ObjectMapper
的初始化塊。
3.1 ObjectMapper 的線程安全
????????官方文檔明確指出:ObjectMapper
在配置完成后是線程安全的。因此工具類將其聲明為 static final
,避免重復創建帶來的元數據開銷(SerializerProvider
、DeserializerCache
等)。但需注意,若在運行時調用 setXxx
方法修改配置,則線程安全假設將被打破。
3.2 空 Bean 與日期格式
MAPPER.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS).setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
-
FAIL_ON_EMPTY_BEANS
默認開啟,當對象無任何可序列化屬性時拋異常。關閉后,此類對象會被序列化為{}
,避免 DTO 在演進過程中因新增字段全部@JsonIgnore
而意外崩潰。 -
SimpleDateFormat
非線程安全,但ObjectMapper
會將其包裹成線程局部變量,因此配置一次即可。
3.3 異常處理:IOException 的簡化
????????Jackson 的 writeValueAsString
聲明拋出 JsonProcessingException
(繼承 IOException
)。工具類同樣將其轉換為 IllegalArgumentException
,與 FastJSON 保持行為統一,降低上層心智負擔。
四、橫向對比:FastJSON vs Jackson
維度 | FastJSON | Jackson |
---|---|---|
性能 | 高(ASM 生成字節碼) | 中高(3.x 版本已大幅優化) |
默認日期格式 | 時間戳 | 時間戳 |
循環引用處理 | 默認使用?$ref | 默認拋出?JsonMappingException |
泛型反序列化 | TypeReference | TypeReference (同名類) |
安全配置(autoType) | 曾出現 RCE 漏洞,需開啟 safemode | 默認白名單機制,漏洞面更小 |
社區活躍度 | 國內高,國際一般 | 國際主流,Spring 默認 |
擴展性 | 支持?SerializeFilter ?等擴展 | 模塊機制豐富(Joda、Kotlin 等) |
注:性能差異在大多數業務場景下可忽略,應優先考慮可維護性與安全。
五、從工具類到項目落地:一個完整的演進故事
????????假設某電商系統早期采用 FastJSON,后因安全審計要求全面遷移至 Jackson。若直接使用框架 API,則改動面巨大;而借助 JsonUtils
,僅需替換實現即可。
5.1 遷移步驟
- 保留原有
JsonUtils
類簽名,內部實現替換為 Jackson。 - 通過全局搜索驗證無直接調用
JSON.parseXxx
的代碼。 - 運行單元測試,重點觀察日期格式、Long 型精度、BigDecimal 精度是否變化。
- 灰度發布,通過日志比對線上 JSON 輸出差異。
5.2 兼容性陷阱
-
浮點精度:FastJSON 默認關閉
WriteNullNumberAsZero
,Jackson 需手動配置SerializationFeature.WRITE_NULL_NUMBERS_AS_ZERO
。 -
Long 精度:前端 JavaScript 最大安全整數為 2^53-1,后端 Long 超過此范圍需序列化為字符串。FastJSON 可配置
BrowserCompatible
,Jackson 需自定義ToStringSerializer
。
六、再談防御式編程:邊界條件的“三重門”
工具類雖小,卻肩負第一道防線。以下三點常被忽視:
-
null 與空串:FastJSON 允許
JSON.parseObject("", clazz)
返回 null,而 Jackson 會拋異常。工具類統一返回 null,避免調用方差異。 -
異常日志:必須記錄原始 JSON 片段,但需脫敏(如手機號、身份證)。可引入 SPI 機制,讓業務模塊提供
SensitiveDataFilter
。 -
線程局部泄漏:若使用 ThreadLocal 緩存
SimpleDateFormat
,務必在 Tomcat 熱部署時調用remove
,防止類加載器泄露。
七、小結與展望
????????序列化是“數據在 JVM 與網絡之間最后一公里”的工程。FastJSON 與 Jackson 各有千秋,工具類則是屏蔽差異、沉淀團隊規范的最佳載體。未來隨著 Java 21 的 Vector API、Project Valhalla 的 value objects 落地,序列化的底層實現或將迎來新一輪變革。但萬變不離其宗:統一配置、防御式編程、可觀測三板斧,仍將長期適用。
????????希望這篇 3000 字左右的梳理,能為你下一次技術選型或代碼審查,提供一把“小而鋒利”的瑞士軍刀。