輕鬆上手Jackjson(珍藏版)

xiezhr發表於2024-04-07

寫在前面

雖然現在市面上有很多優秀的json解析庫,但 Spring預設採用Jackson解析Json。

本文將透過一系列通俗易懂的程式碼示例,帶你逐步掌握 Jackson 的基礎用法、進階技巧以及在實際專案中的應用場景。

一、Jackjson簡介

Jackson 是當前用的比較廣泛的,用來序列化和反序列化 json 的 Java 的開源框架。

什麼是序列化和反序列化呢?

  • 序列化:將 Java Bean 轉換為JSON 字串
  • 反序列化:將JSON字串轉換為JavaBeen物件

GitHub地址:https://github.com/FasterXML/jackson

從GitHub 看到,目前有8.8k stars,最近更新時間是2個月前。可見其更新速度還是比較活躍的

也是json最流行的解析器之一

二、Jackjson優點

  • Jackson 所依賴的 jar 包較少,簡單易用
  • 其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 檔案速度比較快
  • Jackson 執行時佔用記憶體比較低,效能比較好
  • Jackson 有靈活的 API,可以很容易進行擴充套件和定製

三、Jackjson模組及依賴

① Jackson庫包括三個主要的模組

  • jackson-databind用於資料繫結
  • jackson-core用於JSON解析和生成
  • jackson-annotations用於註解支援

② 所需依賴

jackson-databind 依賴 jackson-core 和 jackson-annotations,所以可以只顯示地新增jackson-databind依賴,jackson-core 和 jackson-annotations 也隨之新增到 Java 專案工程中

maven

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.15.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.15.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

Gradle

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.4'
}

四、快速上手

假設我們有一個簡單的 User 類:

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String name;
    private int age;
}

4.1 Java物件轉JSON字串

@Test
public void testSerializationExample() throws JsonProcessingException {
    User user = new User("小凡", 18);
    ObjectMapper objectMapper = new ObjectMapper();
    String userstr = objectMapper.writeValueAsString(user);
    System.out.println(userstr);
}
//輸出
{"name":"小凡","age":18}

4.2 JSON字串轉Java物件

@Test
public void testDeserializationExample() throws JsonProcessingException {
    String json ="{\"name\":\"小凡\",\"age\":18}";
    ObjectMapper objectMapper = new ObjectMapper();
    User user = objectMapper.readValue(json, User.class);
    System.out.println(user);

}
//輸出
User(name=小凡, age=18)

上面例子中我們我們使用了ObjectMapper 它是 Jackson 庫的核心類,負責實現 Java 物件與 JSON 文字之間的相互轉換

五、Jackjson序列化API

5.1 普通Java物件序列化

@Test
public void testObjectToJson() throws JsonProcessingException {
    User user = new User();
    user.setName("小凡");
    user.setAge(18);
    ObjectMapper mapper = new ObjectMapper();
    String userstr = mapper.writeValueAsString(user);
    System.out.println(userstr);
}
//輸出
{"name":"小凡","age":18}

5.2 複雜物件序列化

① 構造作者出版資訊物件

//書籍物件
@Data
public class Book {
    private String bookName;
    private String publishDate;
    private String publishHouse;
    private Double price;

}
//地址物件
@Data
public class Address {
    private String city;
    private String street;
}
//作者出版資訊物件
@Data
public class PublishInfo {
    private String name;
    private String sex;
    private Integer age;
    private Address addr;
    private List<Book> books;
}

② 複雜物件轉JSON字串

@Test
public void testComplexObjectToJson() throws JsonProcessingException {

    //構造所有出版的書籍list
    ArrayList<Book> books = new ArrayList<Book>();
    Book book1 = new Book();
    book1.setBookName("Java從入門到放棄");
    book1.setPublishDate("2004-01-01");
    book1.setPublishHouse("小凡出版社");
    book1.setPrice(66.66);

    Book book2 = new Book();
    book2.setBookName("Spring從入門到入土");
    book2.setPublishDate("2024-01-01");
    book2.setPublishHouse("小凡出版社");
    book2.setPrice(88.88);

    books.add(book1);
    books.add(book2);

    //構造作者地址資訊
    Address addr = new Address();
    addr.setCity("昆明");
    addr.setStreet("xxx區xxx路xxx號");

    //構造作者出版的所有書籍資訊
    PublishInfo publishInfo = new PublishInfo();
    publishInfo.setName("小凡");
    publishInfo.setSex("男");
    publishInfo.setAge(18);
    publishInfo.setAddr(addr);
    publishInfo.setBooks(books);

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(publishInfo);
    System.out.println(json);
}
//返回
{"name":"小凡","sex":"男","age":18,"addr":{"city":"昆明","street":"xxx區xxx路xxx號"},"books":[{"bookName":"Java從入門到放棄","publishDate":"2004-01-01","publishHouse":"小凡出版社","price":66.66},{"bookName":"Spring從入門到入土","publishDate":"2024-01-01","publishHouse":"小凡出版社","price":88.88}]}

5.3 List集合序列化

 @Test
public void testListToJson() throws JsonProcessingException {
    User user1 = new User();
    user1.setName("小凡001");
    user1.setAge(18);

    User user2 = new User();
    user2.setName("小凡002");
    user2.setAge(30);

    ArrayList<User> users = new ArrayList<>();
    users.add(user1);
    users.add(user2);

    ObjectMapper mapper = new ObjectMapper();
    String userstr = mapper.writeValueAsString(users);
    System.out.println(userstr);

}
//輸出
[{"name":"小凡001","age":18},{"name":"小凡002","age":30}]

5.4 Map集合序列化

@Test
public void testMapToJson() throws JsonProcessingException {
    User user = new User();
    user.setName("小凡");
    user.setAge(18);

    List<String> asList = Arrays.asList("抽菸", "喝酒", "燙頭髮");
    HashMap<String, Object> map = new HashMap<>();
    map.put("user", user);
    map.put("hobby",asList);

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(map);
    System.out.println(json);
}
//輸出
{"user":{"name":"小凡","age":18},"hobby":["抽菸","喝酒","燙頭髮"]}

@Test
public void testMapToJsonSup() throws JsonProcessingException {
    User user1 = new User();
    user1.setName("小凡001");
    user1.setAge(18);

    User user2 = new User();
    user2.setName("小凡002");
    user2.setAge(30);

    ArrayList<User> users = new ArrayList<>();
    users.add(user1);
    users.add(user2);


    List<String> asList = Arrays.asList("抽菸", "喝酒", "燙頭髮");
    HashMap<String, Object> map = new HashMap<>();
    map.put("users", users);
    map.put("hobby",asList);
    map.put("name","張三");

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(map);
    System.out.println(json);
}
//輸出
{"name":"張三","users":[{"name":"小凡001","age":18},{"name":"小凡002","age":30}],"hobby":["抽菸","喝酒","燙頭髮"]}

5.5 日期處理

預設情況下,jackjson會將日期型別屬性序列化成long型值(自1970年1月1日以來的毫秒數)。顯然這樣格式的資料不符合人類直觀檢視

假設我們有個Person物件

@Data
public class Person {
    private String name;
    private Date birthday;
}

① 我們先來看看預設轉換的結果

@Test
public void testDateToJsonDefault() throws JsonProcessingException {
    Person person = new Person();
    person.setName("小凡");
    person.setBirthday(new Date());

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
    System.out.println(json);

}
//輸出
{"name":"小凡","birthday":1712220896407}

② 透過SimpleDateFormat 將日期格式化成人類可看格式顯示

@Test
public void testDateToJson() throws JsonProcessingException {
    Person person = new Person();
    person.setName("小凡");
    person.setBirthday(new Date());

    ObjectMapper mapper = new ObjectMapper();
    //進行一下日期轉換
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    mapper.setDateFormat(dateFormat);
    String json = mapper.writeValueAsString(person);
    System.out.println(json);

}
//輸出  這個格式就人性化多了
{"name":"小凡","birthday":"2024-04-04"}

六、Jackjson反序列化API

Jackson透過將JSON欄位的名稱與Java物件中的getter和setter方法進行匹配,將JSON物件的欄位對映到Java物件中的屬性。

Jackson刪除了getter和setter方法名稱的“ get”和“ set”部分,並將其餘名稱的第一個字元轉換為小寫。

6.1 普通JSON字串反序列化

6.1.1 JSON字串->Java物件

注: 這裡我們還是使用前面小節中建立的User 實體類

@Test
public void testStrToObject() throws JsonProcessingException {
    String json ="{\"name\":\"小凡\",\"age\":18}";
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(json, User.class);
    System.out.println(user);
}
//輸出
User(name=小凡, age=18)
6.1.2 字元輸入流-->Java物件
@Test
public void testReaderToObject() throws JsonProcessingException {
    String json ="{\"name\":\"小醫仙\",\"age\":18}";
    Reader reader = new StringReader(json);
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(json, User.class);
    System.out.println(user);
}
//輸出
User(name=小醫仙, age=18)
6.1.3 位元組輸入流->Java物件

① 建立user001.json檔案,檔案內容如下

image-20240404204833787

②將位元組輸入流轉換為User物件

@Test
public void testInputStreamToObject() throws IOException {
    FileInputStream inputStream = new FileInputStream("F:\\vueworkspace\\jackjson-demo\\jackjson-demo\\src\\json\\user001.json");
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(inputStream, User.class);
    System.out.println(user);
	}
}
//輸出
User(name=蕭炎, age=18)
6.1.4 JSON 檔案反->Java物件

①我們準備一個user.json檔案,內容如下

image-20240404173608592

② 讀取user.json檔案中內容,並轉換成User 物件

@Test
public void testJsonfileToObject() throws IOException {
    File file = new File("F:\\vueworkspace\\jackjson-demo\\jackjson-demo\\src\\json\\user.json");
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(file, User.class);
    System.out.println(user);

}
//輸出
User(name=蕭炎, age=18)
6.1.5 URL檔案->Java物件

① 我們在網路上放一個user.json資原始檔,內容如下

{"name":"紫妍","age":18}

② 透過URL(java.net.URL) 將JSON轉換成User物件

@Test
public void testUrlToObject() throws IOException {
    String url ="https://files.cnblogs.com/files/blogs/685650/user.json";
    URL url1 = new URL(url);
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(url1, User.class);
    System.out.println(user);
}
//輸出
User(name=紫妍, age=18)
6.1.6 位元組陣列-> java物件
@Test
public void testByteToObject() throws IOException {
    String json ="{\"name\":\"韓雪\",\"age\":18}";
    byte[] bytes = json.getBytes();
    ObjectMapper mapper = new ObjectMapper();
    User user = mapper.readValue(bytes, User.class);
    System.out.println(user);

}
//輸出
User(name=韓雪, age=18)

6.2 JSON陣列字串 反序列化

6.2.1 JSON字串->Map集合
@Test
public void testJsonStrToMap() throws JsonProcessingException {
    //{"name":"小凡","sex":"男","age":18}
    String json ="{\"name\":\"小凡\",\"sex\":\"男\",\"age\":18}";
    ObjectMapper mapper = new ObjectMapper();
    Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
    System.out.println(map);
}
//輸出
{name=小凡, sex=男, age=18}
6.2.2 JSON陣列字串->List集合
@Test
public void testJsonArrToList() throws JsonProcessingException {
    //[{"name":"小凡001","age":18},{"name":"小凡002","age":30}]
    String json ="[{\"name\":\"小凡001\",\"age\":18},{\"name\":\"小凡002\",\"age\":30}]";
    ObjectMapper mapper = new ObjectMapper();
    List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {
    });
    System.out.println(users);
}
//輸出
[User(name=小凡001, age=18), User(name=小凡002, age=30)]
6.2.3 JSON陣列字串->Java物件資料
@Test
public void testJsonArrToObjArr() throws JsonProcessingException {
    //[{"name":"小凡001","age":18},{"name":"小凡002","age":30}]
    String json ="[{\"name\":\"小凡001\",\"age\":18},{\"name\":\"小凡002\",\"age\":30}]";
    ObjectMapper mapper = new ObjectMapper();
    User[] users = mapper.readValue(json, User[].class);
    for (User user : users) {
        System.out.println(user);
    }
}
//輸出
User(name=小凡001, age=18)
User(name=小凡002, age=30)

七、自定義序列化反序列化

透過自定義序列化和反序列化可以使其更加靈活多變

7.1 自定義序列化

有時候,我們不需要jackjson預設的序列化方式。例如,JSON中使用與Java物件中不同的屬性名稱,

或者不需要Java物件中的某個欄位,這時我們就需要自定義序列化器

我們先來看看User實體物件的定義如下

@Data
public class User {
    private String name;
    private Integer age;
}

按照下面預設序列化程式碼,我們最後得到的JSON字串如下

@Test
public void testSerializationExample() throws JsonProcessingException {
    User user = new User("小凡", 18);
    ObjectMapper objectMapper = new ObjectMapper();
    String userstr = objectMapper.writeValueAsString(user);
    System.out.println(userstr);
}
//輸出
{"name":"小凡","age":18}

而現在的需求升級了,再不建立或修改Users實體物件的情況下,我們想要得到{"username":"小凡","userage":18} 這樣的字串,

應該怎麼辦呢?

這時,我們就需要自定義序列化器,就可以輕鬆實現,具體程式碼如下

 @Test
public void testDefineSerialize() throws JsonProcessingException {

    SimpleModule version1Module = new SimpleModule();
    version1Module.addSerializer(User.class, new JsonSerializer<User>() {
        @Override
        public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeStartObject();
            jsonGenerator.writeStringField("username", user.getName());
            jsonGenerator.writeNumberField("userage", user.getAge());
            jsonGenerator.writeEndObject();
        }
    });

    ObjectMapper objectMapper = new ObjectMapper();

    objectMapper.registerModule(version1Module);

    User user = new User("小凡", 18);

    String json = objectMapper.writeValueAsString(user); // 使用版本1的序列化器

    System.out.println( json);

}
//輸出
{"username":"小凡","userage":18}

7.2 自定義反序列化器

同理,反序列化也可以自定義,具體程式碼如下

① 自定義一個反序列化器

public class UserDeserializer extends StdDeserializer<User> {

    public UserDeserializer() {
        this(null);
    }

    public UserDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public User deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException {
        JsonNode node = jp.getCodec().readTree(jp);
        String name = node.get("name").asText();
        int age = (Integer) ((IntNode) node.get("age")).numberValue();

        return new User(name, age);
    }
}

②將JSON字串反序列化為 User 物件

@Test
public void testUserDeserializer() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addDeserializer(User.class, new UserDeserializer());
    objectMapper.registerModule(module);

    String json = "{\"name\":\"小凡\",\"age\":30}";
    User user = objectMapper.readValue(json, User.class);

    System.out.println(user);

}
//輸出
User(name=小凡, age=30)

八、樹模型

Jackson具有內建的樹模型,可用於表示JSON物件。Jackson樹模型由JsonNode類表示。

在處理JSON時,我們有時並不直接關心或無法直接對映到特定的Java物件,而是需要對JSON內容進行動態、靈活的操作。

這時,樹模型就派上了用場。我們可以遍歷、查詢、更新或合併JSON資料,而無需預先定義對應的Java類

8.1 JsonNode型別概覽

JsonNode家族包括以下主要子型別:

  • ObjectNode:對應JSON物件,包含一組鍵值對,可以透過.put(key, value)新增或更新屬性。
  • ArrayNode:對應JSON陣列,包含一系列元素,可透過.add(value)插入新元素。
  • TextNodeIntNodeLongNodeDoubleNode等:分別對應JSON中的字串、整數、長整數、浮點數等基本型別值。
  • BooleanNodeNullNode:分別對應JSON中的布林值和null值。

8.2 建立與操作JsonNode例項

下面透過一些具體的程式碼示例,展示如何使用JackJson的樹模型API來建立、操作JsonNode物件。

示例1:建立簡單JsonNode

@Test
public void testJackJsonTreeModelExample(){
    // 建立 ObjectMapper 例項
    ObjectMapper mapper = new ObjectMapper();

    // 建立並初始化各型別 JsonNode 例項
    ObjectNode personNode = mapper.createObjectNode();
    personNode.put("name", "小凡");
    personNode.put("age", 18);

    ArrayNode hobbiesNode = mapper.createArrayNode();
    hobbiesNode.add("寫程式碼").add("看書").add("打豆豆");

    personNode.set("hobbies", hobbiesNode);
    // 輸出構建的 JSON 字串
    System.out.println(personNode.toString());
}
//輸出
{"name":"小凡","age":18,"hobbies":["寫程式碼","看書","打豆豆"]}

上述程式碼首先建立了一個ObjectMapper例項,它是JackJson的核心工具類,負責序列化和反序列化工作。接著,我們建立了一個ObjectNode代表JSON物件,設定了"name"和"age"兩個屬性。然後建立了一個ArrayNode儲存愛好列表,並將其新增到personNode中。最後,列印輸出構建的JSON字串。

示例2:從JSON字串反序列化為JsonNode

@Test
public void testJackJsonTreeModelExample2() throws JsonProcessingException {
    String jsonInput = "{\"name\":\"小凡\",\"age\":18,\"hobbies\":[\"寫程式碼\",\"看書\",\"打豆豆\"]}";

    ObjectMapper mapper = new ObjectMapper();
    JsonNode rootNode = mapper.readTree(jsonInput);
    System.out.println(rootNode.get("name").asText());
    System.out.println(rootNode.get("age").asInt());
    System.out.println(rootNode.get("hobbies").get(0).asText());
    System.out.println(rootNode.get("hobbies").get(1).asText());
    System.out.println(rootNode.get("hobbies").get(2).asText());
}
//輸出
小凡
18
寫程式碼
看書
打豆豆

在此示例中,我們首先定義了一個JSON字串。然後使用ObjectMapperreadTree()方法將其反序列化為JsonNode。接下來,透過呼叫.get(key)方法訪問物件屬性或陣列元素,並使用.asText().asInt()等方法獲取其值。

8.3 利用JackJson樹模型進行深度查詢、修改、合併等操作

上面給出的兩個案例屬於入門級操作,我們學會後可以接著進行一些高階操作

@Test
public   void testJsonManipulationExample() throws JsonProcessingException {
    String JSON_STRING = "{\"name\":\"小凡\",\"age\":18,\"hobbies\":[\"寫程式碼\",\"看書\",\"打豆豆\"]}";
    ObjectMapper mapper = new ObjectMapper();

    // 將JSON字串轉換為JsonNode物件
    JsonNode rootNode = mapper.readTree(JSON_STRING);

    // **深度查詢**
    // 查詢"年齡"
    int age = rootNode.get("age").asInt();
    System.out.println("Age: " + age);

    // 查詢第一個興趣愛好
    String firstHobby = rootNode.get("hobbies").get(0).asText();
    System.out.println("First hobby: " + firstHobby);

    // **修改**
    // 修改年齡為20
    ((ObjectNode) rootNode).put("age", 20);
    // 新增新的興趣愛好:"旅行"
    ((ArrayNode) rootNode.get("hobbies")).add("旅行");

    // **合併**
    // 假設有一個新的JSON片段要與原資料合併
    String additionalJson = "{\"address\":\"北京市\",\"job\":\"程式設計師\"}";
    JsonNode additionalNode = mapper.readTree(additionalJson);

    // 使用ObjectNode#setAll方法將新節點中的鍵值對合併到原始節點中
    ((ObjectNode) rootNode).setAll((ObjectNode) additionalNode);

    // 列印更新後的JSON字串
    System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode));
}
//輸出
Age: 18
First hobby: 寫程式碼
{
  "name" : "小凡",
  "age" : 20,
  "hobbies" : [ "寫程式碼", "看書", "打豆豆", "旅行" ],
  "address" : "北京市",
  "job" : "程式設計師"
}

九、Jackson註解使用

9.1 序列化反序列化通用註解

9.1.1 @JsonIgnore

@JsonIgnore 用於在序列化或反序列化過程中忽略某個屬性

定義實體類Person

public class Person {
    private String name;
    @JsonIgnore // 新增此註解以忽略 password 欄位
    private String password;
    // 構造器、getter/setter 方法等...
}

使用示例

@Test
public void testJsonIgnore() throws Exception{
    Person person = new Person("小凡", "123456");
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
}
//輸出
{
  "name": "小凡"
}

詳細解釋

  • @JsonIgnore 註解直接放在屬性宣告前,如 private String password; 行上方。

  • Person 類的物件被序列化為 JSON 時,password 屬性將不會出現在生成的 JSON 字串中

  • 同樣地,在反序列化過程中,如果 JSON 資料中包含 password 欄位,Jackson 在解析時會自動忽略它,不會嘗試將其值設定到對應的 Person 物件屬性上。

9.1.2 @JsonIgnoreProperties

@JsonIgnore 註解相比,@JsonIgnoreProperties 用於批次指定在序列化或反序列化過程中應忽略的屬性列表,特別適用於應對不明確或動態變化的輸入 JSON 中可能存在但不應處理的額外欄位

定義實體類Person

希望同時忽略 passwordsocialSecurityNumber 兩個敏感屬性

@JsonIgnoreProperties({"password", "socialSecurityNumber"})
public class Person {
    private String name;
    private String password;
    private String socialSecurityNumber;

    // 構造器、getter/setter 方法等...
}

使用示例

@Test
public void testJsonIgnoreProperties() throws Exception{
    Person person = new Person("小凡", "123456","233535");
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
}
//輸出
{
  "name": "小凡"
}

詳細解釋

  • @JsonIgnoreProperties 註解放置在類定義的開始處,作為類級別的註解。
  • 註解內透過一個字串陣列引數列舉出要忽略的屬性名。在這個例子中,{"password", "socialSecurityNumber"} 指定了 passwordsocialSecurityNumber 兩個屬性在序列化和反序列化時應被忽略。
  • 序列化 Person 物件時,生成的 JSON 字串將不包含 passwordsocialSecurityNumber 屬性。與前面 @JsonIgnore 示例類似,JSON 輸出僅包含未被忽略的 name 屬性:
{
  "name": "小凡"
}
  • 反序列化時,即使輸入的 JSON 資料中包含了 passwordsocialSecurityNumber 欄位,Jackson 也會忽略它們,不會嘗試將這些欄位的值填充到相應的 Person 物件屬性中。

@JsonIgnoreProperties 還有一個額外功能

@JsonIgnoreProperties有個可選的屬性 ignoreUnknown,用於控制是否忽略 JSON 中存在但 Java 類中沒有對應的未知屬性。若設定為 true,則在反序列化時遇到未知屬性時會自動忽略,避免丟擲異常

@JsonIgnoreProperties(ignoreUnknown = true)
public class Person {
    // ...
}
9.1.3 @JsonIgnoreType

@JsonIgnoreType 註解在 Jackson 庫中用於指示整個類在序列化或反序列化過程中應當被忽略。這適用於那些由於某種原因(如敏感資訊、內部細節等)不需要或不應該被 JSON 化處理的類

定義CreditCardDetails 實體類

// 標記該類在序列化和反序列化時應被忽略
@JsonIgnoreType
public class CreditCardDetails {
    private String cardNumber;
    private String cvv;
    private String expirationDate;
     // 構造器、getter/setter 方法等...
}

定義Customer

Customer 類,它有一個 creditCardDetails 屬性引用上述 CreditCardDetails 型別的物件:

public class Customer {
    private String customerId;
    private String name;
    private CreditCardDetails creditCardDetails;

    // 構造器、getter/setter 方法等...
}

使用示例

public void testJsonIgnoreType() throws JsonProcessingException {
    Customer customer = new Customer("CUST001", "John Doe", new CreditCardDetails("1234567890123456", "123", "2024-12"));
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(customer);
}
//輸出
{
  "customerId": "CUST001",
  "name": "John Doe"
}

詳細解釋

  • @JsonIgnoreType 註解放置在需要忽略的類定義前,作為類級別的註解。
  • Customer 類物件被序列化為 JSON 時,所有 CreditCardDetails 型別的屬性(如 customer.creditCardDetails)都會被完全忽略,不會出現在生成的 JSON 字串中。例如:

在此例中,生成的 json 字串將不含 creditCardDetails 部分,僅包含 customerIdname

{
  "customerId": "CUST001",
  "name": "John Doe"
}

同理,在反序列化過程中,如果 JSON 資料中包含 CreditCardDetails 型別的巢狀結構,Jackson 解析時會自動忽略這部分內容,不會嘗試建立或填充對應的 CreditCardDetails 物件

9.1.4 @JsonAutoDetect

@JsonAutoDetect 用於自定義類的屬性(包括欄位和 getter/setter 方法)在序列化和反序列化過程中的可見性規則。預設情況下,Jackson 只使用 public 的欄位和 public 的 getter/setter 方法。透過使用 @JsonAutoDetect,你可以根據需要調整這些規則,以適應不同類的設計和資料模型。

定義實體類 Employee

@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.NONE,   // 不使用欄位
    getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC, // 允許 protected 和 public 的 getter
    setterVisibility = JsonAutoDetect.Visibility.ANY,   // 允許任意訪問許可權的 setter
    isGetterVisibility = JsonAutoDetect.Visibility.DEFAULT,  // 預設行為(public)
    creatorVisibility = JsonAutoDetect.Visibility.DEFAULT) // 預設行為(public)
public class Employee {

    protected String id;
    String department; // package-private 欄位

    // 非標準命名的 getter
    public String getIdentification() {
        return id;
    }

    // 非標準命名的 setter
    public void setIdentification(String id) {
        this.id = id;
    }

    //...標準getter setter

}

使用示例

@Test
public  void testJsonAutoDetect() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    Employee employee = new Employee();
    employee.setId("E001");
    employee.setDepartment("Sales");

    String json = mapper.writeValueAsString(employee);
    System.out.println(json);
}
//輸出
{
  "identification": "E001",
  "department": "Sales"
}

詳細解釋

  • 在上述 Employee 類的例子中:
    • fieldVisibility = JsonAutoDetect.Visibility.NONE 指定不使用任何欄位,意味著即使有 public 欄位,也不會被 Jackson 處理。
    • getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC 允許 Jackson 使用 protectedpublic 的 getter 方法。
    • setterVisibility = JsonAutoDetect.Visibility.ANY 指定可以使用任意訪問許可權的 setter 方法。
    • isGetterVisibilitycreatorVisibility 使用預設行為,即只處理 public 的 is-getter 方法和建構函式。
  • 因為我們將 id 欄位的 getter 和 setter 設為了非標準命名(getIdentification()setIdentification()),且指定了 getterVisibility = JsonAutoDetect.Visibility.PROTECTED_AND_PUBLIC,所以 Jackson 能正確識別並處理這兩個方法,儘管它們不是標準的 get 字首命名。
9.1.5 @JsonProperty

用於指定類的屬性在序列化和反序列化成 JSON 時所對應的鍵名

Book 實體類

假設有一個 Book 類,其中包含 titleyearPublished 欄位。我們希望在序列化和反序列化時,將 yearPublished 欄位以 publishedYear 作為 JSON 鍵名。可以使用 @JsonProperty 註解進行重新命名:

public class Book {
    private String title;
    @JsonProperty("publishedYear") // 重新命名 yearPublished 欄位為 publishedYear
    private int yearPublished;
    // 構造器、getter/setter 方法等...
}

使用示例:

現在建立一個 Book 物件並序列化為 JSON:

public void testJsonProperty() throws Exception {
    Book book = new Book();
    book.setTitle("小凡程式設計語錄");
    book.setYearPublished(1951);

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(book);
    System.out.println(json);
}
//輸出
{
  "title": "小凡程式設計語錄",
  "publishedYear": 1951
}

詳細解釋

在本例中,@JsonProperty("publishedYear") 表明 yearPublished 欄位在 JSON 中應稱為 publishedYear

反序列化時,當遇到 JSON 中的 publishedYear 鍵時,Jackson 會知道應該將其值賦給 Book 類的 yearPublished 欄位。

9.1.6 @JsonFormat

用於指定日期、時間、日期時間以及其他數值型別在序列化和反序列化為 JSON 時的格式

建立 Student

public class Student {
    // 其他屬性...

    // 使用 @JsonFormat 註解的 LocalDate 屬性
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate birthday;
    // 構造器、getter、setter 等...
}

使用示例

注:Jackson預設不支援Java 8的日期時間型別java.time.LocalDate,如果使用jdk8需要引入如下依賴.

<dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
        <version>最新版本號</version> <!-- 替換為當前最新的穩定版本 -->
</dependency>

除了引入以來外,還需要確保在使用Jackson進行序列化和反序列化時,註冊JavaTimeModule

mapper.registerModule(new JavaTimeModule());

@Test
public void testJsonFormat() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 建立 Jackson 的 ObjectMapper 例項
    mapper.registerModule(new JavaTimeModule());

    // 序列化示例
    Student student = new Student();
    student.setName("小凡");
    student.setAge(18);
    student.setBirthday(LocalDate.of(1990,1,1)); // 設定使用者的出生日期
    String json = mapper.writeValueAsString(student); // 將使用者物件轉換為 JSON 字串
    System.out.println(json); // 輸出序列化後的 JSON 資料

    // 反序列化示例
    String inputJson = "{\"birthday\":\"1990-01-01\"}";
    Student deserializedUser = mapper.readValue(inputJson, Student.class); // 從 JSON 字串反序列化為 User 物件
    System.out.println(deserializedUser.getBirthday()); // 輸出反序列化後獲得的出生日期
}
//輸出
{"name":"小凡","age":18,"birthday":"1990-01-01"}
1990-01-01

詳細解釋

@JsonFormat 註解在 Jackson 中被用來精確控制日期/時間型別的屬性在 JSON 序列化和反序列化過程中的格式。透過給定合適的 shapepattern 引數,可以確保日期資料在 Java 型別與 JSON 文字之間準確無誤地轉換。

9.2 序列化註解

9.2.1 @JsonInclude

用於控制物件在序列化過程中包含哪些屬性。它可以防止空或特定值的欄位被序列化到JSON輸出中,從而幫助您精簡JSON資料結構,減少不必要的傳輸量或避免向客戶端暴露不必要的資訊

定義PersonInclude實體類

@JsonInclude(JsonInclude.Include.NON_NULL)
public class PersonInclude {

    /**
     * 姓名欄位,由於未指定@JsonInclude註解,因此繼承類級別的NON_NULL策略
     */
    private String name;

    /**
     * 職業欄位,顯式指定為僅在非空時才包含在序列化結果中
     */
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private String occupation;

    /**
     * 興趣愛好列表,遵循類級別的NON_NULL策略
     */
    private List<String> hobbies;

    // 建構函式、getter/setter等省略...

}

使用示例

@Test
public void testJsonInclude() throws JsonProcessingException {
    PersonInclude personFull = new PersonInclude();
    personFull.setName("小凡");
    personFull.setOccupation("程式設計師");
    personFull.setHobbies(Arrays.asList("程式設計","看書","打豆豆"));

    PersonInclude personPartial = new PersonInclude();
    personPartial.setName(null);
    personPartial.setOccupation("保潔員");


    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(personFull);
    System.out.println(json);

    String json1 = mapper.writeValueAsString(personPartial);
    System.out.println(json1);


}
//輸出
{"name":"小凡","occupation":"程式設計師","hobbies":["程式設計","看書","打豆豆"]}
{"occupation":"保潔員"}

詳細解釋

  • 測試用例1:序列化後,JSON只包含nameoccupationhobbies欄位都不為空,所以都顯示出來
  • 測試用例2:建立了一個部分欄位為null或空的PersonInclude例項。為空的不顯示
9.2.2 @JsonGetter

用於告訴Jackson,應該透過呼叫getter方法而不是透過直接欄位訪問來獲取某個欄位值

建立PersonGetter實體類

public class PersonGetter {
    private String firstName;
    private String lastName;

    // 使用 @JsonGetter 定義一個方法,該方法將在序列化時作為 "fullName" 屬性的值返回
    @JsonGetter("fullName")
    public String getFullName() {
        return this.firstName + " " + this.lastName;
    }
   // 建構函式和其他常規setter/getter方法
}

使用示例

@Test
public void testJsonGetter() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 建立 Jackson 的 ObjectMapper 例項

    PersonGetter person = new PersonGetter("張", "小凡");// 建立一個 Person 物件

    String json = mapper.writeValueAsString(person); // 將 Person 物件序列化為 JSON 字串

    System.out.println(json); // 輸出序列化後的 JSON
}
//輸出
{"firstName":"張","lastName":"小凡","fullName":"張 小凡"}

@JsonGetter 註解成功地將 getFullName() 方法的結果對映到 JSON 物件中的 "fullName" 屬性。

9.2.3 @JsonAnyGetter

用於標記一個方法,獲取除已知屬性外的所有其他鍵值對。這些鍵值對通常儲存在一個 Map 結構中,

以便將它們作為一個附加的物件進行序列化。

建立CustomData實體類

@Data
public class CustomData {
    private final Map<String, Object> additionalProperties = new HashMap<>();

    // 使用 @JsonAnyGetter 定義一個方法,該方法將在序列化時返回所有額外屬性
    @JsonAnyGetter
    public Map<String, Object> getAdditionalProperties() {
        return additionalProperties;
    }

    // 提供方法來新增或修改額外屬性
    public void addProperty(String key, Object value) {
        additionalProperties.put(key, value);
    }
}

使用示例

 @Test
public void testJsonAnyGetter() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 建立 Jackson 的 ObjectMapper 例項

    CustomData data = new CustomData();
    data.addProperty("key1", "value1");
    data.addProperty("key2", 42);
    data.addProperty("key3", "value3");

    String json = mapper.writeValueAsString(data); // 將 CustomData 物件序列化為 JSON 字串

    System.out.println(json); // 輸出序列化後的 JSON
}
//輸出
{"key1":"value1","key2":42,"key3":"value3"}

@JsonAnyGetter 註解的主要作用如下:

  • 動態屬性支援:透過在返回 Map 的方法上使用 @JsonAnyGetter,您可以將一個物件的動態屬性集合序列化為 JSON 物件的多個鍵值對。這些屬性可能是在執行時新增的,或者基於某些條件動態生成的。
  • 簡化結構:避免為每個可能的動態屬性單獨宣告欄位和 getter/setter。只需維護一個 Map,即可處理任意數量和型別的額外屬性。
  • 相容性與靈活性:當需要與未知或未來可能變化的資料結構互動時,@JsonAnyGetter 可確保 JSON 表示能夠容納未預定義的屬性,從而提高系統的相容性和適應性。
9.2.4 @JsonPropertyOrder

用於指定類中屬性在序列化為 JSON 時的排序規則

建立OrderPerson 實體類

public class OrderPerson {
    @JsonProperty("firstName")
    private String givenName;

    @JsonProperty("lastName")
    private String familyName;
    // 建構函式和其他常規setter/getter方法
}

使用示例

 @Test
public void testJsonPropertyOrder() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 建立 Jackson 的 ObjectMapper 例項

    OrderPerson person = new OrderPerson("John", "Doe"); // 建立一個 OrderedPerson 物件

    String json = mapper.writeValueAsString(person); // 將 OrderedPerson 物件序列化為 JSON 字串

    System.out.println(json); // 輸出序列化後的 JSON
}
//輸出
{"firstName":"John","lastName":"Doe"}

@JsonPropertyOrder 註解按照指定順序("lastName""firstName")對 JSON 物件的屬性進行了排序。

9.2.5 @JsonRawValue

用於標記一個欄位或方法返回值,指示Jackson在序列化時應將其原始值視為未經轉義的 JSON 字串,並直接嵌入到輸出的 JSON 文件中。即使所標記的值包含 JSON 特殊字元(如雙引號、反斜槓等),也不會對其進行轉義

建立一個包含 @JsonRawValue 註解的類

public class RawJsonValueExample {
    private String normalProperty = "這是一個正常字串";

    // 使用 @JsonRawValue 標記 rawJsonProperty,使其內容被視為未經轉義的 JSON 值
    @JsonRawValue
    private String rawJsonProperty = "{\"key\": \"value\", \"array\": [1, 2, 3]}";

    // 建構函式和其他常規setter/getter方法
}

使用示例

@Test
public void testJsonRawValue() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper(); // 建立 Jackson 的 ObjectMapper 例項

    RawJsonValueExample example = new RawJsonValueExample(); // 建立一個 RawJsonValueExample 物件

    String json = mapper.writeValueAsString(example); // 將 RawJsonValueExample 物件序列化為 JSON 字串

    System.out.println(json); // 輸出序列化後的 JSON
}
//輸出
{"normalProperty":"這是一個正常字串","rawJsonProperty":{"key": "value", "array": [1, 2, 3]}}

rawJsonProperty 的值並未被轉義,而是作為一個完整的 JSON 物件直接嵌入到父 JSON 文件中。

當您需要在 JSON 物件中嵌入另一段 JSON 文字時,使用 @JsonRawValue 可以確保這段文字以未經轉義的形式出現在輸出的 JSON 中,保持其原有的 JSON 結構。

9.2.6 @JsonValue

用於標記一個方法或欄位,Jackson在序列化該類例項時,直接使用該方法的返回值或欄位的值作為整個物件的 JSON 表示,而非按照類的常規屬性進行序列化

建立JsonValueExample

public class JsonValueExample {
    private LocalDate date;
    private String formattedDate;

    @JsonValue
    public String asJsonString() {
        return "{\"date\":\"" + date.toString() + "\",\"formatted\":\"" + formattedDate + "\"}";
    }
    // 建構函式和其他常規setter/getter方法
}

使用示例

@Test
public void testJsonValue() throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();

    JsonValueExample example = new JsonValueExample();
    example.setDate(LocalDate.of(2024,4,6));
    example.setFormattedDate("yyyy-MM-dd");

    String json = mapper.writeValueAsString(example);

    System.out.println(json);
}
//輸出
"{\"date\":\"2024-04-06\",\"formatted\":\"yyyy-MM-dd\"}"

asJsonString() 方法上應用註解,指示序列化時應將該方法的返回值作為整個物件的 JSON 表達,忽略類中其他的屬性

9.2.7 @JsonSerialize

於指定類、欄位或方法在序列化過程中使用的自定義序列化邏輯

建立一個包含 @JsonSerialize 註解的類及其自定義序列化器

public class BooleanSerializer extends JsonSerializer<Boolean> {
    @Override
    public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if(aBoolean){
            jsonGenerator.writeNumber(1);
        } else {
            jsonGenerator.writeNumber(0);
        }
    }
}

public class PersonSerializer {
    public long   personId = 0;
    public String name     = "John";

    @JsonSerialize(using = BooleanSerializer.class)
    public boolean enabled = false;
    // 建構函式和其他常規setter/getter方法
}

使用示例

@Test
public void testJsonSerialize() throws JsonProcessingException {
    PersonSerializer person = new PersonSerializer();
    person.setName("小凡");
    person.setPersonId(1001);
    person.setEnabled(true);

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(person);
    System.out.println(json);
}
//輸出
{"personId":1001,"name":"小凡","enabled":1}

透過上面程式碼測試我們可以看到,enabled被序列化為1

9.3 反序列化註解

9.3.1 @JsonSetter

用於標記一個方法或欄位,指示 Jackson 在反序列化過程中應如何設定該屬性的值

建立PersonSetter

public class PersonSetter {
    @JsonSetter("first_name")
    private String firstName;
    @JsonSetter("last_name")
    private String lastName;
  // 其他getter setter toString方法省略...
}

使用示例

@Test
public void testJsonSetter() throws JsonProcessingException {

    String jsonNormal = "{\"first_name\":\"張\", \"last_name\":\"小凡\"}";
    ObjectMapper mapper = new ObjectMapper();
    PersonSetter person = mapper.readValue(jsonNormal, PersonSetter.class);
    System.out.println(person);
}
//輸出
PersonSetter(firstName=張, lastName=小凡)

@JsonSetter 註解提供了靈活的方式來定製Jackson在反序列化期間如何將JSON資料對映到Java物件的屬性

9.3.2 @JsonAnySetter

用於處理反序列化過程中遇到的未知或額外的 JSON 鍵值對。當一個物件的JSON表示中包含無法直接對映到已宣告屬性的鍵時,這個註解可以幫助捕獲並儲存這些額外的資料

定義帶有 @JsonAnySetter 註解的類

@ToString
public class CustomObject {
    private String knownProperty;
    private Map<String, Object> additionalProperties = new HashMap<>();

    // 已知屬性的setter方法
    public void setKnownProperty(String knownProperty) {
        this.knownProperty = knownProperty;
    }

    // 獲取已知屬性的getter方法省略...

    /**
     * @JsonAnySetter註解方法,用於處理反序列化過程中遇到的所有未知屬性。
     *
     * @param key   未知屬性的鍵
     * @param value 未知屬性的值
     */
    @JsonAnySetter
    public void addAdditionalProperty(String key, Object value) {
        additionalProperties.put(key, value);
    }

    // 提供訪問額外屬性的方法
    public Map<String, Object> getAdditionalProperties() {
        return additionalProperties;
    }
}

使用示例

@Test
public void testJsonAnySetter() throws JsonProcessingException {
    String json ="{\"knownProperty\":\"expectedValue\",\"extraField1\":\"someValue\",\"extraField2\":42,\"nestedObject\":{\"key\":\"value\"}}";

    ObjectMapper mapper = new ObjectMapper();

    CustomObject customObject = mapper.readValue(json, CustomObject.class);
    System.out.println(customObject);
}
//輸出
CustomObject(knownProperty=expectedValue, additionalProperties={extraField1=someValue, nestedObject={key=value}, extraField2=42})

@JsonAnySetter註解來應對JSON反序列化過程中可能出現的未知屬性,確保所有資料都能被妥善處理和保留。這種機制特別適用於需要相容動態或擴充套件性較強的JSON輸入場景。

9.3.3 @JsonCreator

用於標記一個構造器、靜態工廠方法或例項方法,使其成為反序列化過程中建立物件例項的入口點。這個註解幫助 Jackson 確定如何根據 JSON 資料構建相應的 Java 物件。

定義帶有 @JsonCreator 註解的類

public class AddressCreator {
    private  String street;
    private  int number;
    private  String city;

    // 構造器上使用@JsonCreator註解,指示Jackson使用此構造器反序列化JSON
    @JsonCreator
    public AddressCreator(@JsonProperty("street") String street,
                   @JsonProperty("number") int number,
                   @JsonProperty("city") String city) {
        this.street = street;
        this.number = number;
        this.city = city;
    }
    // getter setter toString方法省略...
}

使用示例

@Test
public void testCreateor() throws JsonProcessingException {
    String json ="{\"street\":\"呈貢區\",\"number\":123,\"city\":\"昆明\"}";
    ObjectMapper mapper = new ObjectMapper();
    AddressCreator addressCreator = mapper.readValue(json, AddressCreator.class);
    System.out.println(addressCreator);
}
//輸出
AddressCreator(street=呈貢區, number=123, city=昆明)
9.3.4 @JacksonInject

用於在反序列化過程中自動注入依賴項或附加資訊到目標物件。通常用於處理那些不能直接從 JSON 資料中獲取、但又希望在反序列化完成後立即可用的資訊

定義帶有 @JacksonInject 註解的類

public class UserInject {
    private String name;
    private String email;

    // @JacksonInject註解在欄位上,指示Jackson在反序列化過程中注入特定值
    @JacksonInject("defaultEmail")
    private String defaultEmail;

    // 構造器和getter setter toString方法省略...
}

使用示例

@Test
public void testJacksonInject() throws JsonProcessingException {
    // 設定可注入的值
    InjectableValues injectables = new InjectableValues.Std().addValue("defaultEmail", "xiaofan@example.com");

    // 建立ObjectMapper並配置注入值
    ObjectMapper mapper = new ObjectMapper().setInjectableValues(injectables);

    String json = "{\"name\":\"小凡\"}";

    // 反序列化時,defaultEmail欄位會被自動注入指定值
    UserInject userInject = mapper.readValue(json, UserInject.class);

    System.out.println("Name: " + userInject.getName());
    System.out.println("Email: " + userInject.getEmail());
    System.out.println("Default Email: " + userInject.getDefaultEmail());
}
//輸出
Name: 小凡
Email: null
Default Email: xiaofan@example.com

@JacksonInject 註解在Jackson反序列化過程中用於引入外部依賴或預設值,使得這些資訊能夠在反序列化完成後立即可用

9.3.5 @JsonDeserialize

用於在反序列化過程中指定自定義的反序列化器來處理某個欄位或類的特殊邏輯

自定義反序列化器

public class BooleanDeserializer extends JsonDeserializer<Boolean> {
    @Override
    public Boolean deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
        String text = jsonParser.getText();
        if("0".equals(text)) {
            return false;
        }
        return true;
    }
}

@JsonDeserialize註解新增到要為其使用自定義反序列化器的欄位

public class PersonDeserialize {
    public long    id;
    public String  name;

    @JsonDeserialize(using = BooleanDeserializer.class)
    public boolean enabled = false;
    //構造器和getter setter toString方法省略...
}

使用示例

{"id":1001,"name":"小凡","enabled":1}
@Test
public void testJsonDeserialize() throws JsonProcessingException {
    String json ="{\"id\":1001,\"name\":\"小凡\",\"enabled\":1}";
    ObjectMapper mapper = new ObjectMapper();
    PersonDeserialize personDeserialize = mapper.readValue(json, PersonDeserialize.class);
    System.out.println(personDeserialize);
}
//輸出
PersonDeserialize(id=1001, name=小凡, enabled=true)

我們json字串中enabled的值是1,最終反序列化成PersonDeserialize 物件後值變成了ture

本期內容到此就結束了,希望對你有所幫助。我們下期再見 (●'◡'●)

相關文章