SpeedData

JSON Patch

ASP.NET Core Web APIによるJSON Patch操作の応用

2023年4月19日
翻訳: 島田 麻里子

この記事は米Catchpoint Systems社のブログ記事Applying JSON Patch Operations with ASP.NET Core Web APIの翻訳です。
Spelldataは、Catchpointの日本代理店です。
この記事は、Catchpoint Systemsの許可を得て、翻訳しています。


APIは、その柔軟性と統合のしやすさから、現代のデジタルビジネスには欠かせないものとなっています。

Catchpointでは、最新の技術やトレンドを追いかけ、さまざまなAPIを公開して開発者が幅広い操作を行えるようにすることを常に目指しています。
近々、REST APIの最新バージョンをローンチする予定で、既存のバージョンと同等の機能を実現するだけでなく、APIを扱う開発者に向けた新機能や技術的な強化を追加します。
REST APIの変更点のひとつは、Patch操作に関連しており、これによりエンティティを更新することができるようになります。
(以前は、POST操作を使ってユーザがエンティティを更新する方法でした)

このブログでは、エンティティを更新するためにJSON Patchを使用する方法を示し、実装中に問題が発生した場合にそれを特定するお手伝いをします。

このPatch操作のクイックガイドでは以下の内容をカバーしています。

  1. JSON Patch入門
  2. JSON Patchがサポートする操作
  3. 様々なユースケース
  4. 主なポイント

JSON Patch入門

多くのAPIでは、JSONをベースのコンテンツタイプ(リクエストとレスポンス)として使用しています。
JSONは、プログラミングで広く使用されている属性のキー/値のペアのオープン標準であり、HTTPリクエストやレスポンスなどに一般的に使用されています。
例えば、JSONはPOSTやPATCHなどの様々な操作で使用できます。

私たちのAPIは、リクエストボディに標準JSONを受け入れて、リソースの更新を指定します。
典型的なJSONドキュメントには、文字列型、オブジェクト型、配列/リスト型などの異なるタイプの属性があります。
これらの属性は、異なるビジネスニーズに応じて更新されます。

操作には、配列/リストに新しいオブジェクトを追加する、オブジェクトを削除する、完全に更新するか部分的に更新するなどが含まれます。
オブジェクト/文字列などの他のタイプにも同様の操作があります。
JSON Patchドキュメントは、(既存の)JSONドキュメント(ビジネスエンティティ)の一部を追加/置換/削除したい場合に、(REST APIの)Patch操作で特に使用できます。

Newtonsoft JSON Patch Document NuGetパッケージ(Microsoft.AspNetCore.Mvc.NewtonsoftJSON)は、様々なシナリオに対して異なる操作をサポートしています。
ただし、場合によっては異なる動作をすることがありますが、それについては先で説明します。

開発者の前提条件・想定

Rest APIのパラダイム、.Net Core、C#に関する基本的な理解が必要です。

問題点

通常、開発者はさまざまな種類のJSONドキュメントを扱い、それに対してさまざまなPatch操作を適用する必要があります。
JSON Patchドキュメントの操作は、異なるデータ型に対して異なる動作をします。
この記事では、JSON Patchドキュメントを使った様々な操作を簡単に扱う方法と、その制限事項について説明しています。

以下のようなオブジェクトタイプに対してさまざまなPatch操作を適用する方法を見ていきます。

デモアプリケーションのクイックコード設定

.Net coreソリューション「PatchSamples.sln」には、APIプロジェクトとAPI.Testプロジェクトがあります。
Patch例(zip)をダウンロードしてください。

PatchSampleControllerには、様々なPatchメソッドがあります。
モデルAnimalは、下記のanimal.JSONドキュメントを表しています。
あらゆる操作を示すために、複雑なJSONドキュメントを使用します。


{
  "id": 1,
  "name": "Impala",
  "status": {
    "id": 0,
    "name": "alive"
  },
  "description": {
    "type": {
      "id": 98,
      "name": "herbibores"
    },
    "summary": "The impala or rooibok (Aepyceros melampus) is a medium-sized antelope found in eastern and southern Africa",
    "foods": [
      {
        "id": 10,
        "name": "grass"
      },
      {
        "id": 20,
        "name": "tree bark"
      },
      {
        "id": 30,
        "name": "shrubs"
      },
      {
        "id": 40,
        "name": "seeds"
      }
    ]
  }
}	

JSON Patchがサポートする操作

JSON Patchは、リソースに適用される更新を指定する形式です。

JSON Patchドキュメントには、操作の配列があります。
各操作は、特定のタイプの変更を識別します。
そのような変更の例としては、配列要素の追加やプロパティ値の置換があります(Microsoftドキュメントで定義されています)。

様々なユースケースを探る前に、JSON Patchドキュメントクエストスキーマの主要なプロパティを理解することが重要です。
主要なプロパティは以下の通りです。

スキーマのルートは、操作と呼ばれる複雑なオブジェクトのリストを含むJSON配列です。
操作オブジェクトには、以下の主要なプロパティがあります。

  1. 「OP」 - 許可される操作 - 追加、置換、移動、コピーなど。
  2. 「PATH」 - 要素やプロパティを指定するために使用されます。
    常に「/」(ルート)から始まります。
    例えば、添付されたサンプルのJSON.JSONのNameプロパティの場合、パスは/Nameになります。
  3. 「VALUE」 - 値は、文字列プロパティの場合は文字列、配列の場合は要素、プロパティの要素タイプになります。

JSON Patchドキュメント内では、単一のリクエストに複数の操作を含めることができます。

様々なユースケース

では、考えられる様々なユースケースを、まずは見てみましょう。

追加操作

これには以下が含まれます。

文字列の追加操作

"name"プロパティを追加するためのJSON Patchドキュメントリクエストには、"New Name"という値が必要です。


[
  {
    "op": "add",
    "path": "/name",
    "value": "New Name"
  }
]

追加操作は、既存の値を上書きします。

オブジェクトの追加操作

"animalType"プロパティにオブジェクトを値として追加するJSON Patchドキュメントリクエストです。


[
  {
    "op": "add",
    "path": "/description/type/",
    "value": {
      "id": 1,
      "name": "Herbivores"
    }
  }
]

もしプロパティが別のオブジェクトの子である場合、そのオブジェクトの親が初期化されていなければなりません。
上記の例では、「description」がnullの場合、操作は失敗し、次のエラーが返されます。


{
  "Root": [
    "For operation 'add', the target location specified by path '/advancedSettings/animalType' was not found."
  ]
}

リストケースでのオブジェクト追加操作(1)

既に初期化されているか、あらかじめ設定された配列/リスト型のプロパティの最後にオブジェクトを追加するJSON Patchドキュメントのリクエストです。


[
  {
    "op": "add",
    "path": "/description/foods/-",
    "value": {
      "id": 5,
      "name": "fruits"
    }
  }
]	

pathプロパティの最後の「-」(ハイフン)に注意してください。

リストケースでのオブジェクト追加操作(2)

初期化されていないか、あらかじめ設定されていない配列/リスト型のプロパティを追加するJSON Patchドキュメントのリクエストです。


[
  {
    "op": "add",
    "path": "/description/foods/",
    "value": [
      {
        "id": 5,
        "name": "fruits"
      },
      {
        "id": 6,
        "name": "branches"
      }
    ]
  }
]

まず、ここで渡される値が配列であることに注意してください。また、これは初期化された "foods" プロパティが必要ではないことを意味します。
つまり、プロパティが nullであっても機能します。
ただし、プロパティが存在する場合、このプロパティの既存の値が置き換えられます(つまり、配列プロパティ全体が上書きされます)。

削除操作

これには以下が含まれます。

文字列の削除操作

この操作により、指定されたパス上のプロパティ値が削除されます。
値がない場合や値がすでにnullである場合、エラーは発生しません。


[
  {
    "op": "remove",
    "path": "/name"
  }
]

パッチを適用したいドキュメントにパスが存在している必要があります。
そうでない場合、エラーが発生します。


[
  {
    "op": "remove",
    "path": "/name"
  }
]

{
  "Root": [
    "The target location specified by path segment 'name' was not found."
  ]
}

{
  "Root": [
    "For operation 'remove', the target location specified by path '/description/type/' was not found."
  ]
}

リストからオブジェクト/要素を削除する操作

以下のリクエストは、パッチドキュメントの配列/リストタイプの要素からアイテムを削除します。


[
  {
    "op": "remove",
    "path": "/description/foods/0"
  }
]

リスト要素 "foods"の後のインデックスに注意してください。
そうでないと、その要素の値が削除されます。

置換操作

実際には、置換操作は「削除」と「追加」の2つの操作の一連です。
この操作では、パスで指定された値が削除され、その後新しい値が追加されます。
したがって、比較的に単に追加操作をリクエストすることができます。

文字列とオブジェクトの置換操作


[
  {
    "op": "replace",
    "path": "/name",
    "value": "replace Name"
  }
]
[
  {
    "op": "replace",
    "path": "/description/type/",
    "value": 1,
    "name": "carnivores"
  }
]

リスト内のオブジェクトの置換操作

以下のリクエストでは、パスの末尾に「-」(ハイフン)が付いており、リストの最後の要素が削除されます。
ハイフンは、特定の要素のインデックス値に置き換えることができます。


[
  {
    "op": "replace",
    "path": "/description/foods/-",
    "value": {
      "id": 8,
      "name": "herbs"
    }
  }
]
[
  {
    "op": "replace",
    "path": "/description/foods/1",
    "value": {
      "id": 8,
      "name": "herbs"
    }
  }
]

リスト内の配列の置換操作

別の配列/リストプロパティに置き換えるには。


[
  {
    "op": "replace",
    "path": "/description/foods/-",
    "value": [
      {
        "id": 7,
        "name": "leaves"
      },
      {
        "id": 8,
        "name": "herbs"
      }
    ]
  }
]

オブジェクトの値は配列型であり、「-」ハイフンの代わりに、要素を対象にしています。

move/copyの操作

move/copyは同じタイプのリクエストで、以下の1つの操作を除いて同様に動作します。

  1. 「move」 - 「From」の場所から「Path」の場所へ値をコピーし、「From」の場所から削除を適用します。
  2. 「copy」 - 「From」の場所から「Path」の場所へ値をコピーします。

[
  {
    "op": "move",
    "from": "/name",
    "path": "/description/summary"
  }
]

[
  {
    "op": "copy",
    "from": "/name",
    "path": "/description/summary"
  }
]

[
  {
    "op": "move",
    "from": "/name",
    "path": "/description/foods/0",
    "path": "/description/foods/1"
  }
]

[
  {
    "op": "copy",
    "from": "/name",
    "path": "/description/foods/0",
    "path": "/description/foods/1"
  }
]

テスト操作

これは、「path」フィールドで指定された「value」をチェックするために使用されるテストです。
チェックに成功すると、「200 OK」が返され、それ以外の場合は、「400 Bad Request」が返されます。
この操作は、ファイルされたものを更新する前に行うことができます。

この操作は、次の操作を実行する前に条件をチェックするために、複数の操作と一緒に使用すると便利です。


[
  {
    "op": "test",
    "path": "/description/foods/0/name",
    "value": "grass"
  }
]

[
  {
    "op": "test",
    "path": "/name",
    "value": "Impala"
  }
]

最初のテスト操作で、インデックスプロパティ(0)に注意してください。

マルチ操作

単一のリクエストに複数の操作が含まれ、それらはすべて異なるタイプであることがあります。


[
  {
    "op": "test",
    "path": "/description/foods/0/name",
    "value": "grass"
  },
  {
    "op": "add",
    "path": "/description/foods/",
    "value": [
      {
        "id": 7,
        "name": "leaves"
      },
      {
        "id": 8,
        "name": "herbs"
      }
    ]
  },
  {
    "op": "replace",
    "path": "/name",
    "value": "Testing patch"
  }
]

マルチ操作は、すべて成功するか、すべて失敗するかのいずれかです。

操作制御

JSON Patch NuGetは、APIエンドポイントがサポートできる特定の操作を許可するために使用できるサポートされる操作のリストを提供します。
また、(カスタマイズを通じて)他のロールと拡張およびマッピングすることもできます。


var supportedOperations = new List<OperationType>
{
	OperationType.Add,
	OperationType.Remove,
	OperationType.Replace
};

OperationTypeを参照するためには、アセンブリMicrosoft.AspNetCore.JSON Patchを参照する必要があります。

JSON Patchドキュメントの動的オブジェクトとの使用方法

JSON Patchドキュメントは動的オブジェクトとも使用できます。


dynamic doc = new ExpandoObject();
doc.IntegerList = new List<int>() {1, 2, 3};

// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("IntegerList", new List<int>() { 4, 5, 6});

JSON Patchドキュメントのインスタンスを作成し、これまでに説明したすべての操作をこのクラスのメソッドとして追加できます。

JSON Patchドキュメントの他の用途

JSON Patchドキュメントは、JSONドキュメントを変更する際にも便利です。


var doc = new SimpleDTOWithNestedDTO()
{
	SimpleDTO = new SimpleDTO()
	{
  		IntegerValue = 5,
  		IntegerList = new List<int>() { 1, 2, 3}
	}
};

var new DTO = new SimpleDTO()
{
	DoubleValue = 1
};

// create patch
JsonPatchDocument patchDoc = new JsonPatchDocument();
patchDoc.Replace("SimpleDTO", newDTO);

または


var patchDoc = new JsonPatchDocument<AdvancedSettings>();
patchDoc.Add(x => x.AppliedTestFlags, new AppliedTestFlag { Id = 1, Name = "Test"});

主なポイント

HTTP Patchを初めて使う方は、このブログでJSON Patchに関する基礎的な知識を身につけ、すでに使っている方は、技術的なノウハウを深め、洗練させることができたら幸いです。

さらに詳しく

Find out about Catchpoint’s Integrations

Check out WebPageTest by Catchpoint’s YouTube channel