知識庫 / Spring / Spring MVC RSS 訂閱

使用 JSON Patch 在 Spring REST API 中

JSON,Spring MVC
HongKong
11
09:52 PM · Dec 05 ,2025

1. 引言

在各種可用的 HTTP 方法中,HTTP PATCH 方法扮演着獨特的角色。它允許我們對 HTTP 資源應用部分更新。

在本教程中,我們將學習如何使用 HTTP PATCH 方法以及 JSON Patch 文檔格式來應用部分更新到我們的 RESTful 資源。

2. 用例

讓我們從一個示例 HTTP 客户 資源開始,該資源由以下 JSON 文檔表示:

{ 
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Milk","Eggs"],
    "communicationPreferences": {"post":true, "email":true}
}

假設這位客户的電話號碼已更改,並且客户將新的喜愛產品添加到他們的產品列表。這意味着我們需要僅更新 電話喜愛產品 字段中的 客户 信息。

我們該怎麼做呢?

流行的 HTTP PUT 方法首先浮現在腦海中。但是,由於 PUT 方法會完全替換資源,因此它不適合用於優雅地進行部分更新。此外,客户端必須在應用更新之前執行 GET 操作並保存。

這時,HTTP PATCH 方法就派上用場了。

讓我們來理解 HTTP PATCH 方法和 JSON Patch 格式。

3. HTTP PATCH 方法與 JSON Patch 格式

HTTP PATCH 方法提供了一種方便的方式來應用資源的局部更新。因此,客户端只需要在請求中發送差異即可。

讓我們來看一個簡單的 HTTP PATCH 請求示例:

PATCH /customers/1234 HTTP/1.1
Host: www.example.com
Content-Type: application/example
If-Match: "e0023aa4e"
Content-Length: 100

[description of changes]

HTTP PATCH 請求體描述了目標資源應該如何修改以產生新的版本。 此外,用於表示 更改的格式 因資源類型而異。 { "op":"replace", "path":"/telephone", "value":"001-555-5678" }

每個操作必須包含一個 path 成員。 此外,某些操作對象也必須包含一個 from 成員。 path 及其 from 成員的值是 JSON Pointer。它指向目標文檔內的位置。

這個位置可以指向目標對象中的特定鍵或數組元素。

現在,讓我們 briefly 簡要地看一下可用的 JSON Patch 操作。

4.1. 添加操作

我們使用“添加”操作向對象添加新的成員。 此外,我們還可以使用它來更新現有成員,並將新值插入到指定索引處的數組中。

例如,讓我們將“Bread”(麪包)添加到客户的“ favorites ”(最喜歡)列表中,索引位置為 0:

{
    "op":"add",
    "path":"/favorites/0",
    "value":"Bread"
}

經過add操作後修改後的客户信息如下:

{
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Bread","Milk","Eggs"],
    "communicationPreferences": {"post":true, "email":true}
}

4.2. 刪除操作

刪除 操作會從目標位置移除一個值。此外,它還可以從數組中指定索引處移除一個元素。

例如,讓我們為我們的客户移除 通信偏好

{
    "op":"remove",
    "path":"/communicationPreferences"
}

移除操作後修改後的客户詳情如下:

{
    "id":"1",
    "telephone":"001-555-1234",
    "favorites":["Bread","Milk","Eggs"],
    "communicationPreferences":null
}

4.3. 替換操作

替換操作將目標位置的值更新為新的值。

例如,讓我們為我們的客户更新電話號碼:

{
    "op":"replace",
    "path":"/telephone",
    "value":"001-555-5678"
}

替換操作後修改後的客户信息如下:

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Bread","Milk","Eggs"], 
    "communicationPreferences":null
}

4.4. 移動操作

移動操作會移除指定位置的值,並將該值添加到目標位置。

例如,我們將“Bread”(麪包)從顧客 收藏列表 的頂部移動到列表的底部:

{
    "op":"move",
    "from":"/favorites/0",
    "path":"/favorites/-"
}

移動操作後修改後的客户詳情如下:

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Milk","Eggs","Bread"], 
    "communicationPreferences":null
}

/favorites/0 和 /favorites/- 在上述示例中是 JSON 指針,指向 favorites 數組的起始和結束索引。

4.5. 複製操作

複製操作將指定位置的值複製到目標位置。

例如,讓我們將“Milk”複製到 favorites 列表中:

{
    "op":"copy",
    "from":"/favorites/0",
    "path":"/favorites/-"
}

以下是翻譯後的內容:

執行 複製 操作後修改後的客户詳情如下:

{ 
    "id":"1", 
    "telephone":"001-555-5678", 
    "favorites":["Milk","Eggs","Bread","Milk"], 
    "communicationPreferences":null
}

4.6. 測試操作

該測試操作驗證“路徑”處的數值是否等於“值”。由於 PATCH 操作具有原子性,如果任何操作失敗,則 PATCH 操作應被丟棄。測試操作可用於驗證前提條件和後置條件是否已滿足。

例如,讓我們測試客户電話字段的更新是否成功:

{
    "op":"test", 
    "path":"/telephone",
    "value":"001-555-5678"
}

現在我們來看一下如何將上述概念應用於我們的示例。

5. 使用 JSON Patch 格式的 HTTP PATCH 請求

我們將回顧我們的 客户 用例。

以下是使用 JSON Patch 格式對客户的 電話收藏 列表進行部分更新的 HTTP PATCH 請求:

curl -i -X PATCH http://localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[
    {"op":"replace","path":"/telephone","value":"+1-555-56"},
    {"op":"add","path":"/favorites/0","value":"Bread"}
]'

最重要的是,JSON Patch 請求的 Content-Typeapplication/json-patch+json。 此外,請求體是一個 JSON Patch 操作對象的數組:

[
    {"op":"replace","path":"/telephone","value":"+1-555-56"},
    {"op":"add","path":"/favorites/0","value":"Bread"}
]

我們如何在服務器端處理此類請求?

一種方法是編寫自定義框架,該框架按順序評估操作,並將它們作為原子單元應用於目標資源。 顯然,這種方法聽起來很複雜。 此外,它也可能導致非標準化的補丁文檔消費方式。

幸運的是,我們不必手動構建 JSON Patch 請求的處理過程。

Java API for JSON Processing 1.0,或 JSON-P 1.0,最初定義於 JSR 353,引入了對 JSON Patch 的支持,在 JSR 374 中。

JSON-P API 提供 JsonPatch 類型來表示 JSON Patch 實現。

但是,JSON-P 只是一個 API。 要使用 JSON-P API,我們需要使用實現它的庫。 我們將使用一個名為 json-patch 的庫,用於本文中的示例。

現在,讓我們看看如何構建一個使用 JSON Patch 格式消耗 HTTP PATCH 請求的 REST 服務。

6. 在 Spring Boot 應用程序中實現 JSON Patch

JSON Patch 是一種用於對 JSON 數據的修改標準。它允許客户端通過發送一系列操作來修改服務器端存儲的 JSON 數據。Spring Boot 提供了強大的支持來處理 JSON Patch 操作,使開發者能夠輕鬆地實現對 JSON 數據的增刪改查。

以下是一些使用 Spring Boot 處理 JSON Patch 的關鍵概念和步驟:

  • JSON Patch 規範: JSON Patch 規範定義了用於修改 JSON 數據的操作類型,例如 add, remove, replace, test 等。
  • Spring Data REST: Spring Data REST 可以與 JSON Patch 結合使用,提供 RESTful API 來處理 JSON Patch 操作。
  • 自定義處理器: 為了處理特定的 JSON Patch 操作,可以創建自定義處理器,並在處理器中實現相應的邏輯。
  • 驗證: 在應用 JSON Patch 操作之前,需要對操作進行驗證,以確保操作的有效性和安全性。

下面是一個簡單的示例,展示瞭如何在 Spring Boot 應用程序中使用 JSON Patch:

// Example JSON Patch payload
String patch = "[{\"op\":\"replace\",\"path\":\"/name\",\"value\":\"John Doe\"}]";

// ... (代碼實現 JSON Patch 的處理邏輯) ...

6.1. 依賴項

最新版本的 json-patch 可從 Maven 中央倉庫獲取。

首先,我們將依賴項添加到 pom.xml 中:

<dependency>
    <groupId>com.github.java-json-tools</groupId>
    <artifactId>json-patch</artifactId>
    <version>1.12</version>
</dependency>

現在,讓我們定義一個模式類來表示 Customer JSON 文檔:

public class Customer {
    private String id;
    private String telephone;
    private List<String> favorites;
    private Map<String, Boolean> communicationPreferences;

    // standard getters and setters
}

接下來,我們將查看我們的控制器方法。

6.2. REST 控制器方法

然後,我們可以為我們的客户用例實現 HTTP PATCH:

@PatchMapping(path = "/{id}", consumes = "application/json-patch+json")
public ResponseEntity<Customer> updateCustomer(@PathVariable String id, @RequestBody JsonPatch patch) {
    try {
        Customer customer = customerService.findCustomer(id).orElseThrow(CustomerNotFoundException::new);
        Customer customerPatched = applyPatchToCustomer(patch, customer);
        customerService.updateCustomer(customerPatched);
        return ResponseEntity.ok(customerPatched);
    } catch (JsonPatchException | JsonProcessingException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    } catch (CustomerNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
    }
}

現在我們來理解一下這個方法的運作原理:

  • 首先,我們使用 @PatchMapping 標註方法作為 PATCH 處理方法
  • 當帶有 application/json-patch+json “Content-Type” 的 PATCH 請求到達時,Spring Boot 使用默認的 MappingJackson2HttpMessageConverter 將請求 payload 轉換為 JsonPatch 實例。 這樣,我們的控制器方法將收到請求 body 作為 JsonPatch 實例

在方法內部:

  1. 首先,我們調用 customerService.findCustomer(id) 方法來查找客户記錄
  2. 隨後,如果找到客户記錄,我們調用 applyPatchToCustomer(patch, customer) 方法。 這將 JsonPatch 應用到客户身上(稍後會詳細介紹)
  3. 然後,我們調用 customerService.updateCustomer(customerPatched) 方法來保存客户記錄
  4. 最後,我們返回一個 200 OK 響應給客户端,響應中包含修補後的 Customer 詳情

最重要的是,真正的魔法發生在 applyPatchToCustomer(patch, customer) 方法中:

private Customer applyPatchToCustomer(
  JsonPatch patch, Customer targetCustomer) throws JsonPatchException, JsonProcessingException {
    JsonNode patched = patch.apply(objectMapper.convertValue(targetCustomer, JsonNode.class));
    return objectMapper.treeToValue(patched, Customer.class);
}
  1. 首先,我們有我們的 JsonPatch 實例,它包含要應用於目標 Customer 的操作列表。
  2. 然後,我們將目標 Customer 轉換為 com.fasterxml.jackson.databind.JsonNode 實例,並將其傳遞給 JsonPatch.apply 方法以應用補丁。在幕後,JsonPatch.apply 處理將操作應用於目標。補丁的結果也是一個 com.fasterxml.jackson.databind.JsonNode 實例。
  3. 然後,我們調用 objectMapper.treeToValue 方法,該方法將補丁後的 com.fasterxml.jackson.databind.JsonNode 中的數據綁定到 Customer 類型。 這是一個補丁後的 Customer 實例。
  4. 最後,我們返回補丁後的 Customer 實例。

現在,讓我們運行一些針對我們 API 的測試。

6.3. 測試

首先,我們使用 POST 請求向我們的 API 創建一個客户:

curl -i -X POST http://localhost:8080/customers -H "Content-Type: application/json" 
  -d '{"telephone":"+1-555-12","favorites":["Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}'

我們收到一個 201 Created 響應:

HTTP/1.1 201
Location: http://localhost:8080/customers/1

Location響應頭設置為新資源的地址。它指示新Customerid為1。

接下來,讓我們使用PATCH請求更新此客户的部分信息:

curl -i -X PATCH http://localhost:8080/customers/1 -H "Content-Type: application/json-patch+json" -d '[
    {"op":"replace","path":"/telephone","value":"+1-555-56"}, 
    {"op":"add","path":"/favorites/0","value": "Bread"}
]'

我們收到帶有修改後的客户信息的 200 OK 響應:

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 14 Feb 2020 21:23:14 GMT

{"id":"1","telephone":"+1-555-56","favorites":["Bread","Milk","Eggs"],"communicationPreferences":{"post":true,"email":true}}

7. 結論

在本文中,我們探討了如何在 Spring REST API 中實現 JSON Patch。

首先,我們研究了 HTTP PATCH 方法及其在執行部分更新方面的能力。

然後,我們瞭解了什麼是 JSON Patch,並掌握了各種 JSON Patch 操作。

最後,我們討論瞭如何使用 json-patch 庫在 Spring Boot 應用程序中處理 HTTP PATCH 請求。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.