JSONata式を掘ってみる

debugノードの記事でチラッと紹介した「JSONata式」をもう少し見てみます(「じぇいそなた」と読むんでしょうか)。JSONata式は「JSONオブジェクトからデータを抽出するためのクエリ」とのこと。JSON配列から特定の条件を満たすオブジェクトを取り出すみたいなシチュエーションはよくあると思いますが、JSONata式を使うと「for文でループ回して〜プロパティの値を比較して〜」みたいなことをやらなくても済むのでコードがスッキリしそうです。

今回は、リファレンスの中から便利そうだと思った機能をいくつかピックアップします。全機能を知るにはリファレンスを参照してください。

クエリ対象のJSONオブジェクト

injectノードでmsg.payloadに以下のようなJSONオブジェクトを格納しておきます。これに対していろいろなクエリを適用して結果を取得してみます。
{
    "FirstName": "Fred",
    "Surname": "Smith",
    "Age": 28,
    "Address": {
        "Street": "Hursley Park",
        "City": "Winchester",
        "Postcode": "SO21 2JN"
    },
    "Phone": [
        {
            "type": "home",
            "number": "0203 544 1234"
        },
        {
            "type": "office",
            "number": "01962 001234"
        },
        {
            "type": "office",
            "number": "01962 001235"
        },
        {
            "type": "mobile",
            "number": "077 7700 1234"
        }
    ],
    "Email": [
        {
            "type": "work",
            "address": [
                "fred.smith@my-work.com",
                "fsmith@my-work.com"
            ]
        },
        {
            "type": "home",
            "address": [
                "freddy@my-social.com",
                "frederic.smith@very-serious.com"
            ]
        }
    ],
    "Other": {
        "Over 18 ?": true,
        "Misc": null,
        "Alternative.Address": {
            "Street": "Brick Lane",
            "City": "London",
            "Postcode": "E1 6RF"
        }
    }
}

気になったクエリ

プロパティ指定

プロパティ名をドットで区切って値を取得できます。これは直感的かと思います。
  • クエリ
  • payload.Address.City
  • 結果
  • "Winchester"

配列要素+プロパティ指定

配列要素を指定して、さらにその中のプロパティを指定するパターンです。配列要素を決め打ちで指定する場面はそんなにないと思いますが、これも直感的ですね。
  • クエリ
  • payload.Phone[0].number
  • 結果
  • "0203 544 1234"

複数要素のプロパティ

ひとつ上の例と似ていますが、配列要素指定を省いたパターンです。こうすると、指定したプロパティが配列で返ってきます。面白い挙動ですが超便利そうです。
  • クエリ
  • payload.Phone.number
  • 結果
  • [ "0203 544 1234", "01962 001234", "01962 001235", "077 7700 1234" ]

プロパティが条件を満たすオブジェクト

プロパティが特定の条件を満たすオブジェクトを返すクエリです。これが一番「クエリ」っぽい感じがします。かなり便利そう。
  • クエリ
  • payload.Phone[type='mobile']
  • 結果
  • { type: "mobile", number: "077 7700 1234" }

四則演算

数値データの四則演算ができます。他にも絶対値計算などいろいろな演算ができます。ほぼSQLレベル。
  • クエリ
  • payload.Age * 2
  • 結果
  • 56

比較

比較演算子も使えます。JavaScriptでif文を書かなくてもフロー制御ができそうです。
  • クエリ
  • payload.Age > 20
  • 結果
  • true

ソート

配列をソートできます。すごーい。
  • クエリ
  • $sort(payload.Phone.number)
  • 結果
  • [ "01962 001234", "01962 001235", "0203 544 1234", "077 7700 1234" ]

現在時刻(ISO 8601)

現在時刻も返せます。
  • クエリ
  • $now()
  • 結果
  • "2021-09-18T01:19:53.594Z"

正規表現

ここまで来ると単純なクエリレベルを超えている感じ。
  • クエリ
  • $match(payload.Other."Alternative.Address".City, /.*Lon.*/)
  • 結果
  • { match: "London", index: 0, groups: array[0] }

Base64

Node.jsでBase64を使おうと思ったらBufferを使いますが、そんなのやらなくていいってマジかよ。
  • クエリ
  • $base64encode(payload.Other."Alternative.Address".City)
  • 結果
  • "TG9uZG9u"

まとめ

JSONata便利です!Node.jsアプリケーション上で単独利用できるようなので、何かの機会に使ってみようと思います。

フロー

最後に、今回作成したフローを晒しておきます。Node-REDで読み込むと再現できます。
[
    {
        "id": "cad6f983ee9de5e2",
        "type": "tab",
        "label": "フロー 1",
        "disabled": false,
        "info": ""
    },
    {
        "id": "1039ae098b363b1d",
        "type": "inject",
        "z": "cad6f983ee9de5e2",
        "name": "",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "{\"FirstName\":\"Fred\",\"Surname\":\"Smith\",\"Age\":28,\"Address\":{\"Street\":\"Hursley Park\",\"City\":\"Winchester\",\"Postcode\":\"SO21 2JN\"},\"Phone\":[{\"type\":\"home\",\"number\":\"0203 544 1234\"},{\"type\":\"office\",\"number\":\"01962 001234\"},{\"type\":\"office\",\"number\":\"01962 001235\"},{\"type\":\"mobile\",\"number\":\"077 7700 1234\"}],\"Email\":[{\"type\":\"work\",\"address\":[\"fred.smith@my-work.com\",\"fsmith@my-work.com\"]},{\"type\":\"home\",\"address\":[\"freddy@my-social.com\",\"frederic.smith@very-serious.com\"]}],\"Other\":{\"Over 18 ?\":true,\"Misc\":null,\"Alternative.Address\":{\"Street\":\"Brick Lane\",\"City\":\"London\",\"Postcode\":\"E1 6RF\"}}}",
        "payloadType": "json",
        "x": 130,
        "y": 60,
        "wires": [
            [
                "dbb7034bb96aa75b",
                "712714fb156ffa9f",
                "32559f71686d4cc6",
                "896da4b282d3ee9c",
                "22b28fcacfb9836d",
                "29477e59961e925c",
                "3811fdfe63fc0cf8",
                "807b18a16bb451bb",
                "2f048f106dac2af8",
                "bd8d55ad7c14f0c0"
            ]
        ]
    },
    {
        "id": "dbb7034bb96aa75b",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "payload.Phone[type='mobile']",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.Phone[type='mobile']",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 410,
        "y": 180,
        "wires": []
    },
    {
        "id": "712714fb156ffa9f",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "payload.Address.City",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.Address.City",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 380,
        "y": 60,
        "wires": []
    },
    {
        "id": "32559f71686d4cc6",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "payload.Age * 2",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.Age * 2",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 360,
        "y": 220,
        "wires": []
    },
    {
        "id": "896da4b282d3ee9c",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "payload.Age > 20",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "payload.Age > 20",
        "targetType": "jsonata",
        "statusVal": "payload.Age < 28",
        "statusType": "auto",
        "x": 370,
        "y": 260,
        "wires": []
    },
    {
        "id": "22b28fcacfb9836d",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "payload.Phone[0].number",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.Phone[0].number",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 390,
        "y": 100,
        "wires": []
    },
    {
        "id": "29477e59961e925c",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "payload.Phone.number",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload.Phone.number",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 390,
        "y": 140,
        "wires": []
    },
    {
        "id": "3811fdfe63fc0cf8",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "$sort(payload.Phone.number)",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "$sort(payload.Phone.number)",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 410,
        "y": 320,
        "wires": []
    },
    {
        "id": "bbfa4bad2a5f58e6",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "プロパティ指定",
        "info": "",
        "x": 660,
        "y": 60,
        "wires": []
    },
    {
        "id": "fcb51bc614455f1a",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "配列要素+プロパティ指定",
        "info": "",
        "x": 690,
        "y": 100,
        "wires": []
    },
    {
        "id": "a6a40188f80ed29c",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "プロパティの値が条件を満たすオブジェクト",
        "info": "",
        "x": 750,
        "y": 180,
        "wires": []
    },
    {
        "id": "256e1a9505a7df2e",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "四則演算",
        "info": "",
        "x": 640,
        "y": 220,
        "wires": []
    },
    {
        "id": "27561dcebfb4bb7b",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "複数要素のプロパティ",
        "info": "",
        "x": 680,
        "y": 140,
        "wires": []
    },
    {
        "id": "fe599f6b4ac1d09a",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "ソート",
        "info": "",
        "x": 630,
        "y": 320,
        "wires": []
    },
    {
        "id": "add36e637ff787c2",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "比較",
        "info": "",
        "x": 630,
        "y": 260,
        "wires": []
    },
    {
        "id": "807b18a16bb451bb",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "$match()",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "$match(payload.Other.\"Alternative.Address\".City, /.*Lon.*/)",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 340,
        "y": 400,
        "wires": []
    },
    {
        "id": "da41e65fa274f092",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "正規表現",
        "info": "",
        "x": 640,
        "y": 400,
        "wires": []
    },
    {
        "id": "2f048f106dac2af8",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "$now()",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "$now()",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 330,
        "y": 360,
        "wires": []
    },
    {
        "id": "d91097f6632b3373",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "現在時刻(ISO 8601)",
        "info": "",
        "x": 670,
        "y": 360,
        "wires": []
    },
    {
        "id": "bd8d55ad7c14f0c0",
        "type": "debug",
        "z": "cad6f983ee9de5e2",
        "name": "$base64encode()",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "$base64encode(payload.Other.\"Alternative.Address\".City)",
        "targetType": "jsonata",
        "statusVal": "",
        "statusType": "auto",
        "x": 370,
        "y": 440,
        "wires": []
    },
    {
        "id": "cffcdeab0ed65b27",
        "type": "comment",
        "z": "cad6f983ee9de5e2",
        "name": "Base64",
        "info": "",
        "x": 630,
        "y": 440,
        "wires": []
    }
]


コメント

このブログの人気の投稿

Joinノードを使う(その1)

Execノードを使う

Joinノードを使う(その4)