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 )


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

2019年8月10日土曜日

JSON から目的の値を取り出す

画像に含まれるテキストをOCRで抽出するサービスが Google にあることを IBM Champion の小野様に教えていただきました。 


ものは試しにと LotusScript のエージェントでチャレンジしてみたのですが、不慣れな JSON に手間取ってしまったので、ここにメモとして残しておこうと思った次第です。


作成したエージェントの概要ですが、NotesStream に画像ファイルを読み込み Base64 エンコードしたものを、リクエストのボディとして渡す JSON の一部に埋め込み、NotesHTTPRequest を使って指定のエンドポイント(URL)へ JSON を POST します。返ってきた JSON から OCR で取得できたテキストを取り出して表示します。

このエージェントの中で OCR の結果が JSON 形式で戻るのですが、これが私にとってなかなか分かりづらいものでした。 戻ってきた JSON は(実際には2121行もあったのでほぼ省略しますが)次のようになっていました。
{
  "responses": [
    {
      "textAnnotations": [
        {
          "locale": "ja",
          "description": "取り出したいテキストは\nここにあります!\n",
          "boundingPoly": {
            "vertices": [・・・
            ]
          }
        },・・・
      ]
    }
  ]
}

実際に戻ってきた JSON データには、OCR で抽出できた「テキストの全体」を示すものと、「テキストの一部」を示すものがありました。今回取り出したいのはテキストの全体を示すほうです。

そして「テキスト全体」がJSONデータの中に、どうやら2か所にあることがわかりました。どちらを採用するのが正しいのかはさておき、今回はJSONデータの冒頭に見つけた、次の位置の文字列を取り出すことにしました。

「配列 "responses" の最初のエレメントである配列 "textAnnotations" の最初のエレメントにある "description"」


まずは戻ってきた JSON を NotesJSONNavigator などの V10 で追加されたJSONを処理するためのクラス群を使って目的の値を取り出します。

まず私が一つ目の値の取り込みに使用したのは次のようなコードです。変数"JSON"に上記JOSN文字列がセットされてます。

Dim ss As New NotesSession
Dim nav As NotesJSONNavigator
Dim elm As NotesJSONElement
Dim obj As NotesJSONObject
Dim arr As NotesJSONArray

Set nav = ss.CreateJSONNavigator(JSON)
Set elm = nav.Getelementbyname("responses")
Set arr = elm.Value
Set elm = arr.Getfirstelement()
Set obj = elm.Value
Set elm = obj.Getelementbyname("textAnnotations")
Set arr = elm.Value
Set elm = arr.Getfirstelement()
Set obj = elm.Value
Set elm = obj.Getelementbyname("description")
Print elm.Value


取り込みたい JSONObject の名前や配列上の位置が決まっていることが前提なので JSONElement の Type を確認することなくこのようなコードにしています。

位置が「決まっている」とはいえ、少々面倒くさいというか冗長なコードだなという印象です。


ところが、先ほどこちらの資料を読んでいたら、もっとシンプルなコードで実現できることがわかりました。

Dim ss As New NotesSession
Dim nav As NotesJSONNavigator

Set nav = ss.CreateJSONNavigator(JSON)
Set elm = nav.Getelementbypointer( "/responses/0/textAnnotations/0/description" )
Print elm.Value


NotesJSONNavigator クラスの GetElementByPointer メソッドを使うことで、ずいぶんとシンプルになりました。
このとき「配列の最初」を示す数値は「0」となります。

2019年8月9日金曜日

ビューの「索引」

昨晩は「のの会」に参加しました。

実は先月の「のの会」でお話したネタが時間切れのため約半分を次回持越しとしました。
それで持ち越した残り半分を、昨晩お話しました。

そのネタとは「ビュー索引」です。

Notes/Domino の世界で索引と言えば、「全文索引」と「ビュー索引」の2つありますが、今回はビュー索引にスポットをあててみました。

ビュー索引は、パフォーマンスに影響することがしばしばあります。そのため、ビュー索引を維持管理する仕組みには様々な工夫があり、機能改善が続けられています。

お話しながら見ていただいた資料は(のの会でのお話にあわせて)前半/後半に分けて SlideShare へアップしました。

前半は、

  • ビューの構造
  • 索引の維持に関連するビューの設定
  • インデクサ

後半は、

  • 索引を維持するタスクと調整するオプション
  • バージョン9,10における新機能
  • パフォーマンスに係る状況の確認の方法

といった内容となっています。

基本的には、メーカー様のWebサイトの情報やヘルプを読んだ上で、私の理解を書き出したものであり、決してメーカー様の校閲を受けたわけではありません。そのため、もしかしたら内容に誤りがあるかもしれません。内容への自身の無さの表れとしてネタ元へのリンク(IBM様のサイトためそのうちリンク切れするかもですが...)を付けていますので、ネタ元もあわせてご確認ください。