JSON PointerとJSON Patch
AWSのAPI Gatewayを試していたところ、API(API Gatewayの)でJSON Patchなるものを使うことになりました。よくわからんちんでしたが、RFCが出ているということなので読んでみました。以下メモ。
RFC
JSON Pointer
overview
- JSON PointerはJSOCドキュメント中の位置を表す仕様。XMLのXPathみたいなもの。
- ABNFで書くと
- json-pointer =
*( "/" reference-token )
- reference-token =
*( unescaped / escaped )
- unescaped =
%x00-2E / %x30-7D / %x7F-10FFFF
- つまり
%x2F('/')
と%x7E('~')
以外
- つまり
- escaped = "~" ( "0" / "1" )
- 「~0」が'~'
- 「~1」が'/'
- エスケープを外す際は「~1」から「~0」の順に行う。
- json-pointer =
- JSON Patch等で使われる際はJSONドキュメント中に書くので、JSONとしてのエスケープ(ダブルクォート等)も必要になる。
- ざっくり言えば、オブジェクトのキーを
/
区切りで並べたもの。 - 配列の場合は0始まりの添え字まで指定できる。
-
/hoge/fuga/0
,/hoge/fuga/1
, ...といったように。 -
-
を使うと、末尾の添え字+1という意味になる。
次に述べるJSON Patchで最後に挿入といったことを表すために使用する。
-
examples
こんな感じ
pointer_sample.json
{
"hoge" : "this is '/hoge'",
"" : "this is '/'",
"foo/bar" : "this is '/foo~1bar'",
"foo~bar" : "this is '/foo~0bar'",
"fuga" : [
"this is /fuga/0",
"this is /fuga/1"
],
"foo" : {
"bar" : "this is '/foo/bar'"
}
}
JSON Patch
overview
- JSON PatchはJSONドキュメントに対する追加・更新・削除といったオペレーションを表現する。
- JSON Patchはそれ自体がJSONドキュメントとして表現され、「対象のJSONドキュメントに対して実行されるオペレーション」を表すオブジェクトの配列である。
- オペレーションは配列のインデックスの順にシーケンシャルに実行される。
- オペレーションを表すオブジェクトは操作を表す**
op
メンバと操作対象を表すpath
**メンバを必ず含む。-
op
メンバは次のうちから一つadd
remove
replace
move
copy
test
-
path
メンバは上に書いたJSON Pointer
-
- HTTPのPATCHメソッドと一緒にご利用ください。
- RFC5789によると、PATCHメソッドはatomicに実行される必要があるので、オペレーションが一つでも失敗したら全てロールバックしないといけない。
- メディアタイプは
application/json-patch+json
add
**value
**メンバが必須(JSON Value)
-
path
が配列のインデックスを表している場合、value
が指定されたインデックスに挿入される。- この場合、指定されたインデックスに元々入っていた要素およびそれより後ろに入っていた要素は後ろにずれる。
- 末尾に入れる場合は上に書いた
-
を使う。"/array/-"
みたいに。
-
path
が存在しないオブジェクトを表している場合、value
が指定されたオブジェクトとして追加される。- ただし、
/a
というJSON Pointerが存在しているオブジェクトを指していない場合、/a/b
というpath
はinvalidになる。親が存在していることが前提ということか。
- ただし、
-
path
が存在しているオブジェクトを表している場合、value
の値に変更される。「add」だけどupdate? - example
before.json
{ "hoge" : "fuga", "ary" : [ "0", "1" ], "obj" : { "key" : "value" } }
add1.json
{ "op": "add", "path": "/foo", "value": "bar" }
after1.json
{ "hoge" : "fuga", "ary" : [ "0", "1" ], "obj" : { "key" : "value" }, "foo" : "bar" }
add2.json
{ "op": "add", "path": "/ary/-", "value": "2" }
after2.json
{ "hoge" : "fuga", "ary" : [ "0", "1", "2" ], "obj" : { "key" : "value" } }
add3.json
{ "op": "add", "path": "/obj/newkey", "value": "newvalue" }
after3.json
{ "hoge" : "fuga", "ary" : [ "0", "1" ], "obj" : { "key" : "value" , "newkey" : "newvalue"} }
remove
-
path
で指定された要素を削除 -
path
で指定された要素が存在しない場合はエラー - 配列の要素を削除した場合、削除した要素より後ろの要素が前にずれる。
- example
before.json
{ "hoge" : "fuga", "foo" : "bar" }
remove.json
{ "op": "remove", "path": "/hoge" }
after.json
{ "foo" : "bar" }
replace
**value
**メンバが必須(JSON Value)
-
remove
からのadd
と同じ動作とRFCに書いてある。 -
path
が配列以外を指している場合は(remove
せずに)add
するだけと同じ(どちらもupdate)だと思われるが、配列の要素の場合はadd
だとinsertになる一方でreplace
だとupdateになる。 - example
before.json
{ "hoge" : "fuga" }
replace.json
{ "op": "replace", "path": "/hoge", "value": [ "foo", "bar" ] }
after.json
{ "hoge" : [ "foo", "bar" ] }
move
**from
**メンバが必須(JSON Pointer)
-
from
で指定された位置にある要素をpath
で指定された位置に移動する。 -
from
で指定された要素が存在しない場合はエラー -
from
で指定した位置はpath
で指定した位置よりも内側にないといけない、というか自分を自分の子供にすることはできない。 - example
before.json
{ "hoge" : "fuga" }
move.json
{ "op": "move", "path": "/foo", "from": "/hoge" }
after.json
{ "foo" : "fuga" }
copy
**from
**メンバが必須(JSON Pointer)
-
from
で指定された位置にある要素をpath
で指定された位置にコピーする。 -
from
で指定された要素が存在しない場合はエラー - example
before.json
{ "hoge" : "fuga" }
copy.json
{ "op": "copy", "path": "/foo", "from": "/hoge" }
after.json
{ "hoge" : "fuga", "foo" : "fuga" }
test
**value
**メンバが必須(JSON Value)
-
path
で指定された位置にある要素の値が、value
の値と一致するか検査する。 - 一致するかの基準は次の通り(リテラル・ナンバー・文字列は省略)
- 配列: 配列の要素数が一致し、各要素もそれぞれ一致すること。
- オブジェクト: 同じキーを持ち、各キーに対応する値が一致すること。
- JSONとして一致するかなので、下記はそれぞれ一致。
-
[a,b]
と[a, b]
-
{"a":"fuga", "b":"hoge"}
と{"b":"hoge", "a":"fuga"}
-
- example
target.json
{ "hoge" : "fuga" }
```test_sucsess.json
{ "op": "test", "path": "/hoge", "value": "fuga" }
test_error.json
{ "op": "test", "path": "/hoge", "value": "bar" }
所感
- REST APIで操作対象とするリソースをJSON Schemaで公開して、それに対する操作をJSON Patchで受け付けるみたいにするといいかも。
- やりすぎるとSOAPみたいになるかも。
- オブジェクトのやり取りを志向するのではなく、実装とは独立した抽象的なリソースの仕様として使うのが良いか。
- JSON Patch形式でJSONのdiff取ってくれるツール欲しい。