2019年8月13日火曜日

JSON Pointer に翻弄される

前回のエントリ「JSON から目的の値を取り出す」では NotesJSONNavigator クラスの getElementByPointer メソッドを使った例をロクな説明もなしに示しました。

私が翻弄されたのは、JSONのデータに「配列」と「連想配列」が混在することからくるものでした。

この「配列」は、
[ "v1", "v2", "v3" ]
のように「値」を大括弧 [ ] で囲みます。

もうひとつの「連想配列」はというと
{ "k1": "v1", "k2": "v2", "k3": "v3" }
のように「キー」と「値」のペアを中括弧 { } で囲みます。
キーと値のペアで配列を作るところは、LotusScript で例えるなら「リスト」に似ていると思います。


そして getElementByPonter メソッドを使うと、JSON 上の位置を Pointer に指定することで効率よく目的の Element から「値」を取り出すことができます。

実際のコードを示します。
※以下の環境は Community版の Notes 10.0.1 FP2 です

まずは次のような Function を用意しました。
Function getValue( json$, pointer$ ) As Variant
 Dim ss As New NotesSession
 Dim nav As NotesJSONNavigator
 Dim elm As NotesJSONElement
 
 Set nav = ss.Createjsonnavigator( json )
 Set elm = nav.Getelementbypointer( pointer )
 Select Case elm.Type
  Case 1, 2 '1: Object, 2: Array
   MessageBox "Element type is Object or Array"
  Case 3, 4, 5, 64 '3: String, 4: Number, 5: Boolean, 64: Empty
   getValue = elm.Value
  Case Else
   messagebox "undefined type"
 End Select
End Function

配列にある2番目の値を取り出す場合、Pointer に "/1" を指定します。
JSON = |[ "v1", "v2", "v3" ]|
pointer = "/1"
Print getValue( JSON, pointer )

配列の最初は 0、2番目は 1、n番目は n-1 といった数字をスラッシュ"/"の右側に指定します。

連想配列では、キー "k2" の値を取り出す場合、Pointer に "/k2" を指定します。
JSON = |{ "k1": "v1", "k2": "v2", "k3": "v3" }|
pointer = "/k2"
Print getValue( JSON, pointer )

連想配列は、キーを人間が理解しやすい文言にしておくことで、値が取り出しやすくなります。


さて、実際の JSON では、連想配列の「値」の部分が「配列」になっている、といったように、連想配列と配列とが交互に現れたりする場合があります。

これらを区別しながら、連想配列 { } の場合は「キー」を、配列 [] の場合は「位置」を、それぞれ正しく指定しなければなりません。

前回のエントリでも示した次の JSON を見ていきます。
{
  "responses": [
    {
      "textAnnotations": [
        {
          "locale": "ja",
          "description": "取り出したいテキストは\nここにあります!\n",
          "boundingPoly": {
            "vertices": [・・・
            ]
          }
        }
      ]
    }
  ]
}

ここから "description" の「値」を取り出したいので、

  1. 連想配列にあるキー"responses" の値(配列)の、
  2. その配列の最初 "0" の、
  3. 連想配列にあるキー "textAnnotations" の値(配列)の、
  4. その配列の最初 "0" の、
  5. 連想配列にあるキー "description"

という構造になるよう Pointer を組み立てると、次のようになります。
/responses/0/textAnnotations/0/description


ところで Pointer では、連想配列の「キー」や配列の「位置」をスラッシュ"/"で区切っていますが、「キー」にスラッシュ"/"を含む場合はどうしたら?と疑問がわきました。

「きっとバックスラッシュでエスケープするんだろうな」などと安易に試したら見事に外れてしまい...

実はこれ RFC 6901 で仕様化されていました。

スラッシュ "/" とチルダ "~" は特別な意味があることから、
"/" は "~1"
"~" は "~0"
に符号化するのだそうです。

ということで、次のように キーが "Key/0" の場合、Pointer は "/key~10" とするのが正解でした。
JSON = |{ "key/0": false, "Key/1", true }|
pointer = "/key~10"
Print getValue( JSON, pointer )



以下におまけとして、(私にとって?)イレギュラーだったケースを以下に示します。

JSON文字列が(指定できることが正しいかどうかはさておき)配列や連想配列ではない場合、Pointer を "" とすると取れました。
JSON = |"v1"|
pointer = ""
Print getValue( JSON, pointer )

連想配列の「キー」が日本語の場合、Pointer として日本語文字列を指定しても not found (コード 4843)となりました。
JSON = |["キー1", "値1", "キー2", "値2"]|
pointer = "キー2"
Print getValue( JSON, pointer )


「値」が真偽値( True / False )の場合、True / False の最初の文字が小文字になっていないと、 JSON を JSONNavigator へロードしたところでパースできないことを示すエラー(コード 4842)となりました。
JSON = |["1", 2, True]| 'true でないとエラーになる
pointer = "/2"
Print getValue( JSON, pointer )


それから、連想配列や配列の最後に余計なカンマがある場合もパースできないと叱られてしまいました...
文法は厳しくチェックされるようです。

0 件のコメント:

コメントを投稿