Edited at

JSON PointerとJSON Patch

More than 3 years have passed since last update.


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 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取ってくれるツール欲しい。