Spring Boot Jackson 序列化常用配置詳解

一、引言

在當今的 Web 開發領域,JSON(JavaScript Object Notation)已然成為數據交換的中流砥柱。無論是前后端分離架構下前后端之間的數據交互,還是微服務架構里各個微服務之間的通信,JSON 都承擔著至關重要的角色 。它以簡潔的文本格式、輕量級的數據結構和良好的可讀性、可解析性,在眾多數據格式中脫穎而出,被廣泛應用于各類場景。

在 Java 開發體系中,處理 JSON 數據的工具豐富多樣,而 Jackson 則是其中的佼佼者,更是 Spring Boot 默認集成的 JSON 處理工具。Jackson 提供了強大且豐富的 API,能夠便捷地實現 Java 對象與 JSON 數據之間的相互轉換,涵蓋數據綁定、流式處理以及樹模型操作等多項實用功能。借助 Jackson,我們可以輕松地將 HTTP 請求中的 JSON 數據轉換為 Java 對象,也能將 Java 對象轉換為 JSON 格式返回給前端或其他服務。

不過,Jackson 的默認配置在實際開發中往往難以完全契合我們的復雜需求。比如,在處理日期格式時,默認的時間戳表示可能并不符合業務要求;對于空值的處理,默認忽略的方式也許無法滿足特定場景;面對復雜類型和自定義類型,默認的序列化邏輯也可能無法達到預期效果。因此,深入了解并掌握 Jackson 的序列化配置,對于優化應用性能、提升開發效率以及滿足多樣化的業務需求至關重要。接下來,讓我們一同深入探索 Spring Boot 中 Jackson 序列化的常用配置。

二、Jackson 簡介

2.1 Jackson 的定義與功能

Jackson 是一款在 Java 領域中被廣泛應用的高效 JSON 處理庫,它為 Java 開發者提供了一整套便捷、強大的工具,用以輕松實現 Java 對象與 JSON 數據之間的相互轉換 。在當今的軟件開發場景中,尤其是在 Web 應用開發、微服務架構以及數據存儲與傳輸等領域,JSON 作為一種輕量級的數據交換格式,憑借其簡潔易讀、易于解析和生成的特點,成為了數據交互的事實標準。而 Jackson 庫的出現,使得 Java 開發者能夠更加高效地處理 JSON 數據,大大提升了開發效率和應用性能。

Jackson 庫的核心功能之一是數據綁定,它能夠將 JSON 數據準確無誤地映射到 Java 對象,反之亦然。例如,在一個典型的 Web 應用中,前端通過 HTTP 請求向后端發送 JSON 格式的數據,后端使用 Jackson 庫的反序列化功能,將接收到的 JSON 數據轉換為對應的 Java 對象,方便進行業務邏輯處理;處理完成后,再通過 Jackson 庫的序列化功能,將 Java 對象轉換為 JSON 格式的數據返回給前端。這種數據綁定的功能不僅方便了前后端之間的數據交互,還確保了數據的一致性和準確性。

除了數據綁定,Jackson 還支持流式處理,這使得它在處理大型 JSON 數據時表現出色。流式處理允許 Jackson 逐塊讀取和處理 JSON 數據,而無需一次性將整個 JSON 數據加載到內存中,從而大大降低了內存消耗,提高了處理效率。在處理大數據量的 JSON 文件或高并發的 JSON 數據傳輸時,流式處理的優勢尤為明顯。

此外,Jackson 還提供了樹模型支持,它允許開發者將 JSON 數據解析為類似于 DOM 樹的結構,便于對 JSON 數據的各個節點進行靈活操作。通過樹模型,開發者可以方便地讀取、修改、添加或刪除 JSON 數據中的節點,實現對 JSON 數據的精細化處理。比如,在需要對復雜的 JSON 數據進行篩選、合并或轉換時,樹模型能夠提供更加直觀和高效的操作方式。

2.2 Jackson 的核心模塊

Jackson 庫由多個功能強大的模塊組成,這些模塊相互協作,共同為開發者提供了全面的 JSON 處理能力。其中,最為核心的模塊包括 jackson-core、jackson-databind 和 jackson-annotations,它們各自承擔著不同的職責,在 JSON 處理過程中發揮著不可或缺的作用。

jackson-core 是 Jackson 庫的核心基礎模塊,它提供了底層的 JSON 處理功能,定義了低級的流式 API,包括了 JSON 處理細節。該模塊包含了 JsonParser 和 JsonGenerator 兩個關鍵類,JsonParser 用于解析 JSON 數據,它能夠將 JSON 數據逐字符或逐塊地讀取,并將其轉換為一系列的 JSON 令牌(Token),開發者可以通過迭代這些令牌來逐步處理 JSON 數據;JsonGenerator 則用于生成 JSON 數據,它提供了一系列方法,用于將 Java 對象轉換為 JSON 格式的字符串或流。例如,在將一個 Java 對象序列化為 JSON 字符串時,JsonGenerator 會按照 JSON 的語法規則,將對象的屬性和值逐個寫入輸出流中,最終生成完整的 JSON 字符串。jackson-core 模塊的高效實現,為整個 Jackson 庫的高性能提供了堅實的保障。

jackson-databind 是 Jackson 庫中用于數據綁定的核心模塊,也是開發者在日常開發中使用最為頻繁的模塊之一。它在 jackson-core 的基礎上,實現了 Java 對象與 JSON 數據之間的自動映射和轉換,提供了基于 “對象綁定” 解析的相關 API (ObjectMapper)和 “樹模型” 解析的相關 API (JsonNode)。通過 ObjectMapper 類,開發者可以方便地將 JSON 數據轉換為 Java 對象,或者將 Java 對象轉換為 JSON 數據。例如,使用 ObjectMapper 的 readValue 方法,可以將一個 JSON 字符串反序列化為指定類型的 Java 對象;使用 writeValue 方法,則可以將 Java 對象序列化為 JSON 字符串。此外,jackson-databind 還支持復雜對象圖的處理,包括嵌套對象、集合、循環引用等,能夠滿足各種復雜業務場景的需求。

jackson-annotations 是 Jackson 庫的注解模塊,它提供了一系列豐富的注解,用于配置 JSON 序列化與反序列化的行為。這些注解可以直接應用于 Java 類、屬性或方法上,通過簡單的注解配置,開發者能夠靈活地控制 JSON 數據的轉換過程。例如,@JsonIgnore 注解可以用于忽略某個屬性,使其在序列化和反序列化過程中不被處理;@JsonProperty 注解可以用于指定屬性在 JSON 中的名稱,實現屬性名的自定義映射;@JsonFormat 注解則可以用于指定日期、時間等類型的格式化方式,確保數據在 JSON 中的格式符合業務需求。jackson-annotations 模塊的存在,使得 Jackson 庫的使用更加靈活和便捷,能夠滿足不同項目的個性化需求。

在 Spring Boot 項目中,jackson-databind 模塊會被自動引入,這是因為 Spring Boot 默認使用 Jackson 作為 JSON 處理工具,而 jackson-databind 是實現數據綁定的關鍵模塊。通常情況下,開發者無需手動添加該依賴,Spring Boot 會自動完成相關的配置和依賴管理,大大簡化了開發流程。不過,在某些特殊情況下,如需要使用特定版本的 Jackson 庫或添加額外的模塊功能時,開發者可能需要手動調整依賴配置。

三、Spring Boot 中默認的 Jackson 配置

3.1 自動配置

Spring Boot 憑借其強大的自動配置功能,在項目搭建時就默認集成了 Jackson ,這使得開發者無需進行繁瑣的手動配置,即可輕松實現 JSON 數據的處理。當我們在項目中引入spring-boot-starter-web依賴時,Jackson 相關的依賴也會被自動引入,Spring Boot 會自動配置 Jackson 的相關組件,包括ObjectMapper等核心對象,使其能夠在處理 HTTP 請求和響應時無縫對接 JSON 數據轉換工作 。

在一個簡單的 Spring Boot RESTful 應用中,當我們使用@RequestBody注解來接收前端傳遞的 JSON 數據時,Spring Boot 會自動識別并調用 Jackson 的反序列化功能,將 JSON 數據轉換為對應的 Java 對象。例如,有一個User類,包含name和age兩個屬性,前端發送一個包含name和age的 JSON 數據,通過@RequestBody User user即可將 JSON 數據綁定到User對象上,無需手動編寫任何 JSON 解析代碼。同樣,當我們使用@ResponseBody注解返回 Java 對象時,Spring Boot 會自動利用 Jackson 將 Java 對象序列化為 JSON 格式的數據返回給前端 。

3.2 默認行為

Spring Boot 默認的 Jackson 配置會根據 HTTP 請求的Content-Type和Accept頭來智能選擇適當的消息轉換器 。這一機制使得 Spring Boot 能夠靈活地處理不同類型的請求和響應,確保數據的正確傳輸和解析。如果請求的Content-Type為application/json,表明請求數據是 JSON 格式,Spring Boot 會自動選擇 Jackson 作為消息轉換器,將請求體中的 JSON 數據通過 Jackson 反序列化為 Java 對象;在響應時,如果客戶端的Accept頭中包含application/json,表示客戶端期望接收 JSON 格式的數據,Spring Boot 會使用 Jackson 將返回的 Java 對象序列化為 JSON 格式的數據返回給客戶端 。

假設我們有一個UserController,其中包含一個處理用戶創建的方法:

@RestControllerpublic class UserController {@PostMapping("/user")public User createUser(@RequestBody User user) {return user;}}

在上述代碼中,當客戶端發送一個Content-Type為application/json的 POST 請求,請求體為{"name":"張三","age":20}時,Spring Boot 會自動使用 Jackson 將這個 JSON 數據反序列化為User對象,并將該對象傳遞給createUser方法。方法處理完成后,返回的User對象又會被 Jackson 序列化為 JSON 格式的數據返回給客戶端,整個過程無需開發者手動干預,極大地提高了開發效率。

3.3 常見問題與解決方案

在使用 Spring Boot 默認的 Jackson 配置過程中,開發者可能會遇到一些常見問題,其中亂碼問題和日期格式問題較為突出。

當 JSON 數據中包含特殊字符,如中文時,可能會出現亂碼問題。這通常是由于字符編碼不一致導致的。解決這個問題的方法是在配置文件(application.properties或application.yml)中設置正確的字符編碼。在application.properties中添加如下配置:

spring.http.encoding.charset=UTF-8

spring.http.encoding.enabled=true

spring.http.encoding.force=true

上述配置將 Spring 的 HTTP 編碼設置為 UTF-8,并強制應用該編碼,確保在數據傳輸過程中不會出現亂碼問題。

默認情況下,Jackson 在處理日期類型時,可能無法按照我們期望的格式進行序列化和反序列化。比如,默認的日期格式可能是時間戳或者不符合業務需求的格式。為了解決這個問題,我們可以通過在配置文件中進行全局配置來指定日期格式。在application.properties中添加:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss

spring.jackson.time-zone=GMT+8

這里將日期格式指定為yyyy-MM-dd HH:mm:ss,并設置時區為東八區(GMT+8),確保日期在 JSON 中的表示符合我們的業務要求 。通過這種方式,無論是在將 Java 的Date對象序列化為 JSON 字符串,還是將 JSON 中的日期字符串反序列化為Date對象時,都會按照指定的格式進行處理。

四、Jackson 序列化常用配置

4.1 全局配置(通過 application.yml)

在 Spring Boot 項目中,通過application.yml文件對 Jackson 進行全局配置是一種便捷且常用的方式,它能夠統一設置 Jackson 在整個應用中的行為,確保數據的序列化和反序列化符合項目的整體需求 。

在application.yml文件中,我們可以對日期格式、時區、屬性命名策略、空值處理等進行配置。比如,設置日期格式為yyyy-MM-dd HH:mm:ss,時區為東八區(GMT+8),可以這樣配置:

spring:

jackson:

date-format: yyyy-MM-dd HH:mm:ss

time-zone: GMT+8

上述配置中,date-format指定了日期在 JSON 中的序列化和反序列化格式,time-zone設置了應用的時區,確保日期在不同環境下的一致性表示。

對于屬性命名策略,若希望將 Java 對象中的駝峰式命名屬性轉換為 JSON 中的下劃線命名,可以配置:

spring:

jackson:

property-naming-strategy: SNAKE_CASE

在這種配置下,Java 類中的userName屬性,在 JSON 中會被序列化為user_name。

如果希望在序列化時忽略值為null的屬性,可以配置:

spring:

jackson:

default-property-inclusion: non_null

這樣,當 Java 對象中的某個屬性值為null時,在生成的 JSON 數據中該屬性將不會出現 。通過這種全局配置方式,能夠快速有效地對 Jackson 的序列化行為進行統一控制,提高開發效率和代碼的可維護性 。

4.2 注解配置

Jackson 提供了一系列豐富的注解,這些注解能夠在類、屬性或方法級別上靈活地控制序列化和反序列化的行為,為開發者提供了高度的定制化能力 。

@JsonInclude注解用于指定在序列化時哪些值應該被包含。例如,使用@JsonInclude(JsonInclude.Include.NON_NULL)可以確保只有非空值的屬性才會被序列化到 JSON 中。假設我們有一個User類:

@JsonInclude(JsonInclude.Include.NON_NULL)public class User {private String name;private String email;// getters and setters}

當User對象的email屬性為null時,在序列化為 JSON 時,email屬性將不會出現在 JSON 字符串中 。

@JsonFormat注解主要用于自定義日期、時間等類型的格式化方式。在一個包含LocalDateTime類型屬性的Order類中:

public class Order {@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")private LocalDateTime createTime;// getters and setters}

上述配置中,pattern指定了日期時間的格式,timezone設置了時區,這樣在序列化和反序列化createTime屬性時,都會按照指定的格式和時區進行處理 。

@JsonProperty注解用于指定 Java 類的屬性名與 JSON 中的字段名之間的映射關系。例如:

public class User {@JsonProperty("user_name")private String name;// getters and setters}

在這個例子中,Java 對象的name屬性在序列化為 JSON 時,會被映射為user_name字段。

@JsonIgnore注解則用于指定某個 Java 類的屬性在序列化為 JSON 時被忽略。比如:

public class User {private String name;@JsonIgnoreprivate String password;// getters and setters}

在這個User類中,password屬性上添加了@JsonIgnore注解,這意味著在將User對象序列化為 JSON 時,password屬性將不會被包含在 JSON 數據中,有效保護了敏感信息 。

4.3 自定義序列化器和反序列化器

在某些復雜的業務場景中,Jackson 的默認序列化和反序列化規則可能無法滿足需求,此時我們可以通過編寫自定義的序列化器(Serializer)和反序列化器(Deserializer)來實現對特定類或屬性的精確控制 。

自定義序列化器和反序列化器的實現方式是通過實現JsonSerializer和JsonDeserializer接口。以日期處理為例,假設我們希望將Date類型的日期格式化為yyyy-MM-dd的形式,我們可以編寫如下自定義序列化器和反序列化器:

import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;public class CustomDateSerializationExample {public static class DateSerializer extends JsonSerializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {String formattedDate = DATE_FORMAT.format(value);gen.writeString(formattedDate);}}public static class DateDeserializer extends JsonDeserializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {String dateString = p.getText();try {return DATE_FORMAT.parse(dateString);} catch (Exception e) {throw new IOException("Failed to parse date: " + dateString, e);}}}public static class Person {private String name;private Date birthDate;// 省略構造和getter/setter方法}public static void main(String[] args) throws Exception {SimpleModule module = new SimpleModule();module.addSerializer(Date.class, new DateSerializer());module.addDeserializer(Date.class, new DateDeserializer());ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(module);// 序列化Person person = new Person("John Doe", new SimpleDateFormat("yyyy-MM-dd").parse("2000-01-01"));String json = objectMapper.writeValueAsString(person);System.out.println(json); // 輸出:{"name":"John Doe","birthDate":"2000-01-01"}// 反序列化String jsonInput = "{\"name\":\"Jane Smith\",\"birthDate\":\"1990-12-31\"}";Person deserializedPerson = objectMapper.readValue(jsonInput, Person.class);System.out.println(deserializedPerson.getBirthDate()); // 輸出:Mon Dec 31 00:00:00 GMT 1990}}

在上述代碼中,DateSerializer實現了JsonSerializer<Date>接口,重寫了serialize方法,在序列化Date對象時,將其格式化為yyyy-MM-dd的字符串;DateDeserializer實現了JsonDeserializer<Date>接口,重寫了deserialize方法,在反序列化時,將符合yyyy-MM-dd格式的字符串解析為Date對象 。通過創建SimpleModule并將自定義的序列化器和反序列化器注冊到ObjectMapper中,我們就可以在整個應用中使用自定義的日期處理邏輯 。

4.4 使用 Mix-in Annotations

Mix-in Annotations 是 Jackson 提供的一種強大機制,它允許我們在不修改原始類的情況下,為其添加自定義的序列化和反序列化邏輯,這在處理一些無法直接修改源代碼的類或者需要靈活定制序列化規則的場景中非常有用 。

Mix-in Annotations 的原理是通過創建一個獨立的 Mix-in 類,并在該類中為原始類添加自定義的注解,然后將 Mix-in 類與原始類關聯起來。用 Mix-in Annotations 來控制日期格式的序列化和反序列化:

import com.fasterxml.jackson.annotation.JsonFormat;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.core.JsonParser;import com.fasterxml.jackson.databind.DeserializationContext;import com.fasterxml.jackson.databind.JsonDeserializer;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.annotation.JsonDeserialize;import com.fasterxml.jackson.databind.annotation.JsonSerialize;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;public class CustomDateSerializationExample {public static class Person {private String name;private Date birthDate;// 省略構造函數和getter/setter方法}public static class PersonMixin {@JsonSerialize(using = DateSerializer.class)@JsonDeserialize(using = DateDeserializer.class)@JsonFormat(pattern = "yyyy-MM-dd")private Date birthDate;}public static class DateSerializer extends JsonSerializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic void serialize(Date value, JsonGenerator gen, com.fasterxml.jackson.databind.SerializerProvider serializers) throws IOException {String formattedDate = DATE_FORMAT.format(value);gen.writeString(formattedDate);}}public static class DateDeserializer extends JsonDeserializer < Date > {private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");@Overridepublic Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {String dateString = p.getText();try {return DATE_FORMAT.parse(dateString);} catch (Exception e) {throw new IOException("Failed to parse date: " + dateString, e);}}}public static void main(String[] args) throws Exception {ObjectMapper objectMapper = new ObjectMapper();objectMapper.addMixIn(Person.class, PersonMixin.class);// 序列化Person person = new Person();person.setName("John Doe");person.setBirthDate(new SimpleDateFormat("yyyy-MM-dd").parse("2000-01-01"));String json = objectMapper.writeValueAsString(person);System.out.println(json); // 輸出:{"name":"John Doe","birthDate":"2000-01-01"}// 反序列化String jsonInput = "{\"name\":\"Jane Smith\",\"birthDate\":\"1990-12-31\"}";Person deserializedPerson = objectMapper.readValue(jsonInput, Person.class);System.out.println(deserializedPerson.getBirthDate()); // 輸出:Mon Dec 31 00:00:00 GMT 1990}}

在上述代碼中,PersonMixin類為Person類的birthDate屬性添加了自定義的序列化和反序列化注解,通過objectMapper.addMixIn(Person.class, PersonMixin.class)將PersonMixin與Person類關聯起來,從而實現了對Person類birthDate屬性的自定義序列化和反序列化控制,而無需修改Person類的源代碼 。

4.5 使用 ObjectMapper 進行配置

在 Java 代碼中,我們可以通過ObjectMapper對象對 Jackson 進行配置,這種方式提供了更加靈活和動態的配置能力,尤其適用于需要在運行時根據不同的業務場景進行差異化配置的情況 。

ObjectMapper是 Jackson 庫中用于數據綁定的核心類,它提供了豐富的方法來配置序列化和反序列化的行為。我們可以通過ObjectMapper設置日期格式、忽略未知屬性、注冊自定義模塊等。設置日期格式為yyyy-MM-dd,并忽略 JSON 中存在但 Java 對象中不存在的屬性,可以這樣實現:

import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import com.fasterxml.jackson.databind.module.SimpleModule;import java.text.SimpleDateFormat;public class ObjectMapperConfigurationExample {public static void main(String[] args) throws Exception {ObjectMapper objectMapper = new ObjectMapper();// 設置日期格式SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");objectMapper.setDateFormat(dateFormat);// 忽略未知屬性objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 注冊自定義模塊SimpleModule module = new SimpleModule();// 假設這里有自定義的序列化器和反序列化器,進行注冊// module.addSerializer(...);// module.addDeserializer(...);objectMapper.registerModule(module);// 進行序列化和反序列化操作// 例如:// String json = objectMapper.writeValueAsString(someObject);// SomeType obj = objectMapper.readValue(json, SomeType.class);}}

在上述代碼中,通過objectMapper.setDateFormat(dateFormat)設置了日期格式;通過objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)配置反序列化時忽略未知屬性;通過objectMapper.registerModule(module)注冊自定義模塊,為ObjectMapper添加了更多的定制化功能 。通過這種方式,我們可以在 Java 代碼中根據具體需求對 Jackson 進行精細化配置,滿足復雜業務場景的要求 。

五、高級配置與應用場景

5.1 處理復雜數據結構

在實際的業務開發中,我們經常會遇到復雜的數據結構,如嵌套對象和集合。Jackson 憑借其強大的反射機制和靈活的配置能力,能夠輕松應對這些復雜數據結構的序列化和反序列化工作 。

假設我們有一個Department類,它包含一個List<User>集合,用于表示部門中的員工列表。而User類又包含其他屬性,如name、age和address等。這種嵌套的數據結構在實際業務中非常常見,比如在企業管理系統中,一個部門下有多個員工,每個員工有自己的詳細信息 。

public class Department {private String name;private List < User > users;// getters and setters}public class User {private String name;private int age;private String address;// getters and setters}

當我們需要將Department對象序列化為 JSON 時,Jackson 會自動遞歸地處理嵌套的User對象。在調用ObjectMapper的writeValueAsString方法時,Jackson 會遍歷Department對象的所有屬性,對于users屬性,它會進一步遍歷列表中的每個User對象,并將它們轉換為相應的 JSON 鍵值對 。

ObjectMapper objectMapper = new ObjectMapper();Department department = new Department();department.setName("研發部");List<User> users = new ArrayList<>();User user1 = new User();user1.setName("張三");user1.setAge(25);user1.setAddress("北京市海淀區");users.add(user1);User user2 = new User();user2.setName("李四");user2.setAge(30);user2.setAddress("上海市浦東新區");users.add(user2);department.setUsers(users);String json = objectMapper.writeValueAsString(department);System.out.println(json);

上述代碼執行后,輸出的 JSON 字符串如下:

{

"name": "研發部",

"users": [

{

"name": "張三",

"age": 25,

"address": "北京市海淀區"

},

{

"name": "李四",

"age": 30,

"address": "上海市浦東新區"

}

]

}

從輸出結果可以看出,Jackson 能夠準確地將嵌套的對象和集合轉換為 JSON 格式,并且保持數據結構的完整性 。同樣,在反序列化時,Jackson 也能根據 JSON 數據正確地構建出嵌套的 Java 對象結構 。

5.2 解決循環引用問題

在對象關系中,循環引用是一個常見的問題,尤其是在存在雙向關聯的情況下。比如,在一個社交網絡應用中,用戶之間可能存在相互關注的關系,一個User對象可能會包含一個List<User>集合來表示其關注的用戶,而被關注的用戶又可能反過來關注這個用戶,這就形成了循環引用 。如果在序列化時不加以處理,Jackson 會陷入無限遞歸,導致棧溢出錯誤 。

為了解決循環引用問題,Jackson 提供了幾種有效的方式,其中常用的是@JsonIdentityInfo、@JsonManagedReference和@JsonBackReference注解 。

@JsonIdentityInfo注解通過為對象生成唯一標識符,在序列化和反序列化過程中,Jackson 會識別并處理這些標識符,從而避免重復處理同一對象,防止循環引用。假設有兩個類Author和Book,Author類包含一個List<Book>集合表示其創作的書籍,而Book類又包含一個Author屬性表示書籍的作者,形成了雙向關聯 。

import com.fasterxml.jackson.annotation.JsonIdentityInfo;import com.fasterxml.jackson.annotation.ObjectIdGenerators;import java.util.List;@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")public class Author {private Long id;private String name;private List < Book > books;// getters and setters}public class Book {private Long id;private String title;private Author author;// getters and setters}

在上述代碼中,Author類上添加了@JsonIdentityInfo注解,指定使用PropertyGenerator生成器,并以id屬性作為唯一標識符。這樣,在序列化Author對象及其關聯的Book對象時,Jackson 會為每個對象分配一個唯一的標識符,當遇到重復引用時,會使用標識符代替對象本身,從而避免循環引用 。

@JsonManagedReference和@JsonBackReference注解則是通過標記主引用和反向引用的方式來解決循環引用問題。在雙向關聯中,我們可以在其中一個類的屬性上使用@JsonManagedReference注解標記為主引用,在另一個類的對應屬性上使用@JsonBackReference注解標記為反向引用 。在序列化時,主引用會被正常序列化,而反向引用會被忽略,從而打破循環引用 。以User和Friend類為例,User類包含一個List<Friend>集合表示其好友列表,Friend類包含一個User屬性表示好友關系的反向引用 。

import com.fasterxml.jackson.annotation.JsonBackReference;import com.fasterxml.jackson.annotation.JsonManagedReference;import java.util.List;public class User {private Long id;private String name;@JsonManagedReferenceprivate List < Friend > friends;// getters and setters}public class Friend {private Long id;private String name;@JsonBackReferenceprivate User user;// getters and setters}

在這個例子中,User類的friends屬性上使用了@JsonManagedReference注解,Friend類的user屬性上使用了@JsonBackReference注解。當序列化User對象及其好友列表時,friends屬性會被正常序列化,而Friend對象中的user屬性會被忽略,從而避免了循環引用 。

5.3 多態類型的序列化與反序列化

在面向對象編程中,多態是一個重要的特性,它允許我們使用父類的引用指向子類的對象。在 JSON 序列化和反序列化過程中,處理多態類型是一個常見的需求,尤其是在處理繼承關系時 。比如,在一個圖形繪制系統中,可能有一個抽象的Shape類,以及它的具體子類Circle和Rectangle,在進行數據傳輸或存儲時,我們需要能夠正確地序列化和反序列化這些不同類型的Shape對象 。

Jackson 提供了@JsonTypeInfo和@JsonSubTypes注解來處理多態類型。@JsonTypeInfo注解用于指定在序列化和反序列化時如何包含類型信息,@JsonSubTypes注解則用于指定父類的子類型 。

假設有一個抽象類Shape,以及它的兩個子類Circle和Rectangle:

import com.fasterxml.jackson.annotation.JsonSubTypes;import com.fasterxml.jackson.annotation.JsonTypeInfo;@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")@JsonSubTypes({@JsonSubTypes.Type(value = Circle.class, name = "circle"),@JsonSubTypes.Type(value = Rectangle.class, name = "rectangle")})public abstract class Shape {// 公共屬性和方法}public class Circle extends Shape {private double radius;// getters and setters}public class Rectangle extends Shape {private double width;private double height;// getters and setters}

在上述代碼中,Shape類上添加了@JsonTypeInfo注解,指定使用Id.NAME方式來標識類型,將類型信息作為一個名為type的屬性包含在 JSON 中;同時添加了@JsonSubTypes注解,指定了Shape類的兩個子類型Circle和Rectangle,并分別為它們指定了類型名稱circle和rectangle 。

當我們序列化一個Shape對象時,Jackson 會根據對象的實際類型,在 JSON 中添加type屬性來標識其具體類型 。

ObjectMapper objectMapper = new ObjectMapper();

Circle circle = new Circle();

circle.setRadius(5.0);

String json = objectMapper.writeValueAsString(circle);

System.out.println(json);

上述代碼執行后,輸出的 JSON 字符串如下:

{

"type": "circle",

"radius": 5.0

}

在反序列化時,Jackson 會根據type屬性的值,選擇正確的子類來創建對象 。

String jsonInput = "{\"type\":\"rectangle\",\"width\":10.0,\"height\":5.0}";

Shape shape = objectMapper.readValue(jsonInput, Shape.class);

if (shape instanceof Rectangle) {

Rectangle rectangle = (Rectangle) shape;

System.out.println("Rectangle width: " + rectangle.getWidth());

System.out.println("Rectangle height: " + rectangle.getHeight());

}

通過這種方式,Jackson 能夠準確地處理多態類型的序列化和反序列化,確保在不同類型之間進行數據轉換時的準確性和一致性 。

六、性能優化與注意事項

6.1 性能優化建議

在使用 Jackson 進行序列化和反序列化時,采取一些性能優化措施能夠顯著提升應用的整體性能和響應速度,特別是在處理大量數據或高并發請求的場景中 。

盡可能減少不必要的對象創建是提升性能的關鍵。在自定義序列化器和反序列化器中,應避免在每次調用時創建新的對象,而是使用靜態成員變量或單例模式來復用對象。在日期格式化的自定義序列化器中,創建一個靜態的SimpleDateFormat對象,而不是每次序列化時都新建一個實例,這樣可以減少內存開銷和對象創建的時間消耗 。

緩存ObjectMapper對象也是一種有效的優化手段。ObjectMapper的創建和配置通常是比較耗時的操作,尤其是在包含復雜的自定義配置時。通過將ObjectMapper對象緩存起來,避免在每次需要進行序列化或反序列化時重新創建,可以大大提高處理效率 。在一個需要頻繁進行 JSON 數據處理的服務類中,可以將ObjectMapper定義為靜態成員變量,并在類加載時進行初始化,這樣在整個服務類的生命周期內都可以復用該對象 。

根據數據量和應用場景選擇合適的序列化方式也至關重要。對于小型對象和簡單數據結構,普通的對象綁定方式通常就足夠高效;而對于大型數據集合或復雜對象圖,流式處理可能是更好的選擇。流式處理能夠逐塊讀取和處理數據,避免一次性將大量數據加載到內存中,從而降低內存占用,提高處理大型 JSON 數據的效率 。在處理一個包含大量用戶信息的 JSON 文件時,使用流式處理可以在讀取文件的同時進行數據處理,而不需要等待整個文件加載完成,大大提升了處理速度和內存利用率 。

6.2 注意事項

在使用 Jackson 進行序列化和反序列化時,需要注意一些關鍵問題,以確保數據的正確處理和應用的穩定運行 。

注解的使用需要謹慎,不同的注解可能會相互影響,導致意想不到的結果。@JsonFormat和@JsonSerialize注解在處理日期類型時,如果同時使用且配置不一致,可能會造成序列化和反序列化的結果不符合預期。在使用這些注解時,要確保它們的配置相互兼容,并且符合業務需求 。

配置優先級也是一個需要關注的點。全局配置、注解配置以及通過ObjectMapper進行的配置之間存在一定的優先級關系。一般來說,注解配置的優先級高于全局配置,而通過ObjectMapper進行的配置在運行時具有最高的優先級。在進行配置時,要清楚了解這些優先級關系,避免因配置沖突而導致的錯誤 。如果在application.yml中進行了全局的日期格式配置,同時又在某個類的屬性上使用@JsonFormat注解進行了不同的日期格式配置,那么在序列化該屬性時,@JsonFormat注解的配置將生效 。

當處理大對象或復雜對象圖時,性能和內存消耗可能會成為瓶頸。在這種情況下,要注意避免不必要的嵌套和循環引用,合理設計對象結構,以減少序列化和反序列化的時間和內存開銷。可以考慮使用視圖(View)來控制序列化的字段,只返回必要的數據,減少數據傳輸量和處理復雜度 。在一個包含大量屬性和關聯對象的用戶信息類中,如果某些屬性在特定的接口中不需要返回,可以使用@JsonView注解來過濾這些屬性,提高序列化和反序列化的效率 。

七、總結

在 Spring Boot 開發中,Jackson 作為默認的 JSON 處理工具,扮演著舉足輕重的角色。通過本文的詳細介紹,我們深入了解了 Jackson 的核心概念、在 Spring Boot 中的默認配置,以及豐富多樣的序列化常用配置方式,包括全局配置、注解配置、自定義序列化器和反序列化器、Mix-in Annotations 的使用以及通過 ObjectMapper 進行配置等 。這些配置方式為我們在不同的業務場景下靈活處理 JSON 數據提供了強大的支持 。

同時,我們還探討了 Jackson 在處理復雜數據結構、解決循環引用問題以及多態類型的序列化與反序列化等高級應用場景中的應用,以及在性能優化和使用過程中的注意事項 。通過合理地運用 Jackson 的這些特性和配置,我們能夠提升應用的數據處理能力和性能表現,確保數據在不同系統之間的準確傳輸和高效交互 。

希望讀者在實際項目中能夠充分運用這些知識,根據具體的業務需求,靈活配置 Jackson,發揮其最大的優勢。也期待大家在實踐中不斷探索和總結,發現更多關于 Jackson 的高效使用技巧,為項目的開發和優化提供有力的支持 。如果在使用過程中遇到任何問題或有新的見解,歡迎在評論區留言交流,共同進步 。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92234.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92234.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92234.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jetpack ViewModel LiveData:現代Android架構組件的核心力量

引言在Android應用開發中&#xff0c;數據管理和界面更新一直是開發者面臨的重大挑戰。傳統的開發方式常常導致Activity和Fragment變得臃腫&#xff0c;難以維護&#xff0c;且無法優雅地處理配置變更&#xff08;如屏幕旋轉&#xff09;。Jetpack中的ViewModel和LiveData組件正…

Python數據分析案例79——基于征信數據開發信貸風控模型

背景 雖然模型基本都是表格數據那一套了&#xff0c;算法都沒什么新鮮點&#xff0c;但是本次數據還是很值得寫個案例的&#xff0c;有征信數據&#xff0c;各種&#xff0c;個人&#xff0c;機構&#xff0c;逾期匯總..... 這么多特征來做機器學習模型應該還不錯。本次帶來&…

板凳-------Mysql cookbook學習 (十二--------3_2)

3.3鏈接表 結構 P79頁 用一個類圖來表示EmployeeNode類的結構&#xff0c;展示其屬性和關系&#xff1a; plaintext ----------------------------------------- | EmployeeNode | ----------------------------------------- | - emp_no: int …

深度學習圖像預處理:統一輸入圖像尺寸方案

在實際訓練中&#xff0c;最常見也最簡單的做法&#xff0c;就是在送入網絡前把所有圖片「變形」到同一個分辨率&#xff08;比如 256256 或 224224&#xff09;&#xff0c;或者先裁剪&#xff0f;填充成同樣大小。具體而言&#xff0c;可以分成以下幾類方案&#xff1a;一、圖…

pytest-log

問題1&#xff1a;我們在運行測試用例的時候如何記錄測試的log&#xff0c;如何使用&#xff1f;問題2&#xff1a;我寫的函數&#xff0c;為了方便log記錄&#xff0c;但是在pytest運行時&#xff0c;會兼容pytest且不會重復記錄&#xff0c;怎么解決&#xff1f;1、pytest有內…

在安卓源碼中添加自定義jar包給源碼中某些模塊使用

一、具體步驟 1. 準備目錄與 Jar 包 在vendor下 創建新的模塊目錄&#xff0c;放入demo.jar 包&#xff1a; demojar/ # 模塊目錄 ├── Android.bp # 編譯配置文件 └── demo.jar 2. 編寫 Android.bp 配置 Android.bp 示例配置&#xff1a; java_import {…

buntu 22.04 上離線安裝Docker 25.0.5(二)

以下有免費的4090云主機提供ubuntu22.04系統的其他入門實踐操作 地址&#xff1a;星宇科技 | GPU服務器 高性能云主機 云服務器-登錄 相關兌換碼星宇社區---4090算力卡免費體驗、共享開發社區-CSDN博客 兌換碼要是過期了&#xff0c;可以私信我獲取最新兌換碼&#xff01;&a…

初探 Web 環境下的 LLM 安全:攻擊原理與風險邊界

文章目錄前言1 什么是大型語言模型&#xff08;LLM&#xff09;&#xff1f;1.1 LLM的核心特征1.2 LLM在Web場景中的典型應用2 LLM攻擊的核心手段&#xff1a;提示注入與權限濫用3 LLM與API集成的安全隱患&#xff1a;工作流中的漏洞節點3.1 LLM-API集成的典型工作流3.2 工作流…

【新手向】PyTorch常用Tensor shape變換方法

【新手向】PyTorch常用Tensor shape變換方法 前言 B站UP主科研水神大隊長的視頻中介紹了“縫合模塊”大法&#xff0c;其中專門強調了“深度學習 玩的就是shape”。受此啟發&#xff0c;專門整理能夠調整tensor形狀的幾個內置函數&#xff0c;方便以后更好地調整PyTorch代碼中的…

React 18 vs Vue3:狀態管理方案深度對比

?? 背景: React有Redux、Zustand、Jotai等方案 Vue有Pinia、Vuex 4.x 如何選擇適合項目的方案? ?? 核心對比: 維度 React (Redux Toolkit) Vue3 (Pinia) 類型安全 ? 需手動配置TS ? 自動類型推導 代碼量 較多(需寫action) 較少(類似Vuex 5) 響應式原理 不可變數據…

UE5網絡聯機函數

Find Sessions Create Session Join Session Destroy Session Steam是p2p直接聯機 一、steam提供的測試用AppId AppId是steam為每一款游戲所設定的獨有標識&#xff0c;每一款要上架steam的游戲都會擁有獨一無二的AppId。不過為了方便開發者測試&#xff0c;steam提供了游…

Spring Boot 監控:AOP vs Filter vs Java Agent

01前言 在 高并發 微服務 中&#xff0c; 傳統 手動埋點&#xff08;System.currentTimeMillis()&#xff09;就像用體溫計量火箭速度——代碼侵入、重復勞動、維護爆炸。 下文是無侵入、高精度、全鏈路 監控 API 耗時&#xff0c;全程不碰業務代碼的方案&#xff01; 02實戰&…

基于Android的電子記賬本系統

博主介紹&#xff1a;java高級開發&#xff0c;從事互聯網行業多年&#xff0c;熟悉各種主流語言&#xff0c;精通java、python、php、爬蟲、web開發&#xff0c;已經做了多年的畢業設計程序開發&#xff0c;開發過上千套畢業設計程序&#xff0c;沒有什么華麗的語言&#xff0…

7月17日日記

結束了數學建模之后的這兩天一直在緊張的復習&#xff0c;但是說實話效率有點低&#xff0c;因為可能覺得自己找到了兩個小時速成課&#xff0c;覺得無所謂了&#xff0c;所以有點放松了。在宿舍杰哥和林雨城卻一直在復習&#xff0c;感覺他們的微積分和線性代數復習的都比我好…

Linux下SPI設備驅動開發

一.SPI協議介紹1.硬件連接介紹引腳含義&#xff1a;DO(MOSI)&#xff1a;Master Output, Slave Input&#xff0c;SPI主控用來發出數據&#xff0c;SPI從設備用來接收數據。DI(MISO)&#xff1a;Master Input, Slave Output&#xff0c;SPI主控用來發出數據&#xff0c;SPI從設…

用Dify構建氣象智能體:從0到1搭建AI工作流實戰指南

作為一名Agent產品經理,我最近在負責氣象智能體的建設項目。傳統氣象服務面臨三大痛點:數據孤島嚴重(氣象局API、衛星云圖、地面觀測站等多源數據格式不一)、響應鏈路長(從數據采集到預警發布需人工介入多個環節)、交互體驗單一(用戶只能被動接收標準化預警,無法個性化…

Android NDK ffmpeg 音視頻開發實戰

文章目錄接入FFmpeg1.下載FFmpeg 源碼2.編譯FFmpeg.so庫異常處理3.自定義FFmpeg交互so庫創建4.配置CMakeLists.txt5.CMakeLists.txt 環境配置6.Native與Java層調用解碼器準備接入FFmpeg 1.下載FFmpeg 源碼 FFmpeg官網地址 2.編譯FFmpeg.so庫 移動 FFmpeg 源碼文件夾至 Andr…

使用 go-redis-entraid 實現 Entra ID 無密鑰認證

1、依賴與安裝 步驟命令說明安裝&#xff08;或升級&#xff09; go-redis v9.9go get github.com/redis/go-redis/v9latestentraid 必須 ≥ 9.9.0安裝 go-redis-entraidgo get github.com/redis/go-redis-entraid自動拉取 transit 依賴 2、認證方式一覽 方式說明創建 Stream…

window上docker安裝RabbitMQ

1、要進http://localhost:15672管理頁面需要安裝management版本2、搜索鏡像并pull3、啟動鏡像時將端口映射出來4、啟動成功&#xff0c;點擊可查看日志詳情&#xff0c;瀏覽器訪問5、直接使用guest/guest登錄會報錯User can only log in via localhost解決辦法有兩個&#xff1…

異世界歷險之數據結構世界(排序(插入,希爾,堆排))

前言 介紹 插入排序 基本知識&#xff1a; 直接插入排序是一種簡單的插入排序法&#xff0c;其基本思想是&#xff1a; 把待排序的記錄按其關鍵碼值的大小逐個插入到一個已經排好序的有序序列中&#xff0c;直到所有的記錄插入完為止&#xff0c;得到一個新的有序序列 直接插入…