我曾經在端點使用 Dictionary<string,object> 型別,當調用端傳入 {"name":null} 時,移除 name key;傳入 {"name":"123"} 時,name 得的值等於 "123",這樣便能夠做到類似 Json Path 的功能,參考上篇,在不改變合約的情況之下,這次我想要改用 Morcatko.AspNetCore.JsonMergePatch 來實現更新部分資源並且讓端點的合約變成強型別。
開始之前,先簡單科普一下
PATCH HTTP request method
PUT 和 PATCH HttpMethod 不一樣的地方在於 PUT 是替換所有的資源,而 PATCH 是替換指定的屬性
甚麼是 Json Patch
JSON Patch 是一種用於指定要應用於資源的更新的格式,JSON Patch文檔有一組操作,每個操作標識一種特定類型的更改,此類更改的示例包括添加列元素或替換屬性值。
例如,以下 JSON 文檔表示資源、資源的 JSON Patch 文檔以及應用 Patch 操作的結果。
原始
{
"name": "yao",
"foo": "bar"
}
Patch
[
{ "op": "replace", "path": "/name", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/foo"}
]
結果
{
"name": "boo",
"hello": ["world"]
}
一個 JSON Patch 包含了一組 Patch 操作的 JSON 文件。Patch 操作包括 "add"、"remove"、"replace"、"move"、"copy" 和 "test",這些 Patch 操作是按照順序,如果有任何一個操作失敗,整個 Patch 都會被終止。更詳細的內容可以參考 RFC 6902 - JavaScript Object Notation (JSON) Patch (ietf.org)
JSON Pointer IETF RFC 6901 定義了如何在 JSON 文檔中定位指定值的字串符號,用來指定 JSON Patch 要操作的位置,他是使用 / 符號來區分物件的 key 或是陣列的索引,以下為例
{
"biscuits": [
{ "name": "Digestive" },
{ "name": "Choco Leibniz" }
]
}
/biscuits 指向陣列 biscuits,同時 /biscuits/1/name 指向 Choco Leibniz
開發環境
- Rider 2023.1
- ASP.NET Core 7
- Morcatko.AspNetCore.JsonMergePatch.SystemText 6.0.0
特性:- RFC 7396
- performs partial resource update similar to JSON Patch
- Supports Swagger
- netstandard 2.0
實作
安裝套件
新增一個 Web API 專案並安裝以下套件
dotnet add package Morcatko.AspNetCore.JsonMergePatch.SystemText --version 6.0.0
dotnet add package Swashbuckle.AspNetCore.Filters --version 7.0.6
這裡我還有使用到 Swagger Example,不知道 Example 的可以看這篇 [ASP.NET Core 6] 通過 Swashbuckle.AspNetCore 編寫 Web API 的 Swagger 文件 | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)
註冊 JsonMergePatch
builder.Services.AddControllers()
.AddJsonOptions(opts => JsonSerializeFactory.Apply(opts.JsonSerializerOptions))
.AddSystemTextJsonMergePatch(p => p.EnableDelete = true);
註冊 Swagger Example
builder.Services.AddSwaggerGen(p => p.ExampleFilters());
builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());
從某個地方取得的 Employee 資料,內容長的像這樣,這是我原本的資料結構
Employee GetEmployee()
{
var now = DateTimeOffset.Now;
var userId = "Sys";
return new Employee
{
Id = Guid.NewGuid(),
Address = new Address
{
Address1 = "台北市",
Address2 = "大安區",
Street = "忠孝東路"
},
Birthday = new DateTime(2009, 12, 25),
CreatedAt = now,
CreatedBy = userId,
ModifiedAt = now,
ModifiedBy = userId
};
}
Patch 請求的定義還有 Example
public class PatchEmployeeRequest
{
public string? Name { get; set; }
public Address? Address { get; set; }
public DateTime? Birthday { get; set; }
public class PatchEmployeeRequestExample : IExamplesProvider<PatchEmployeeRequest>
{
public PatchEmployeeRequest GetExamples()
{
return new PatchEmployeeRequest
{
Name = "小章",
Address = new Address
{
Address1 = "台北市",
Address2 = "大安區",
Street = "忠孝東路"
},
Birthday = null
};
}
}
}
端點
[HttpPatch]
[SwaggerRequestExample(typeof(PatchEmployeeRequest), typeof(PatchEmployeeRequest.PatchEmployeeRequestExample))]
public async Task<ActionResult> Patch(JsonMergePatchDocument<PatchEmployeeRequest> request)
{
var original = this.GetEmployee();
var patchResult = request.ApplyToT(original);
return this.Ok(patchResult);
}
我要新增 /name,異動 /birthday,移除了 /address/address2
調用端傳入,需要改變的才需要列出來,null 代表要移除
{
"name": "小章",
"Address": {
"Street": null
},
"Birthday": "2009-12-29"
}
結果,如我所預期
{
"id": "74dd6bcb-d3c8-4238-adda-da68c802fd4b",
"name": "小章",
"address": {
"address1": "台北市",
"address2": "大安區"
},
"birthday": "2009-12-29T00:00:00",
"createdAt": "2023-05-08T09:21:40.188945+08:00",
"createdBy": "Sys",
"modifiedAt": "2023-05-08T09:21:40.188945+08:00",
"modifiedBy": "Sys"
}
Postman 執行結果
注意:Content-Type 要選擇 application/merge-patch+json
接下來中斷觀察看看,Morcatko.AspNetCore.JsonMergePatch 可以得到甚麼內容
Json Patch Operation 長這樣,
[
{
"value": "小章",
"OperationType": 2,
"path": "/name",
"op": "Replace"
},
{
"value": {},
"OperationType": 0,
"path": "/Address",
"op": "Add"
},
{
"OperationType": 1,
"path": "/Address/Street",
"op": "Remove"
},
{
"value": "2009-12-29",
"OperationType": 2,
"path": "/Birthday",
"op": "Replace"
}
]
心得
上篇 [.NET 6] 自訂 JsonConverter 反序列化 Dictionary<string, object> | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw) ,我使用反序列化Dictionary<string,obect> 得到哪一個屬性被設定成 null (代表 remove),透過今天分享這個技巧,就可以不需要自己實現反序列化Dictionary<string,obect>,對外的合約也變成是強型別,端點的操作也非常的簡單,不需要真的使用 Json Patch 的 Operations,就能完成部分資源更新。
經實驗,Morcatko.AspNetCore.JsonMergePatch 不支援集合
參考
- JsonPatch in ASP.NET Core web API | Microsoft Learn
- RFC 6902 - JavaScript Object Notation (JSON) Patch (ietf.org)
- Morcatko/Morcatko.AspNetCore.JsonMergePatch: JsonMergePatch support for ASP.NET Core (github.com)
範例位置
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET