Node-RED の ソース を眺めていたところ JSONata ライブラリを見つけました。JSON を操作する簡易言語のようで、なかなか面白いです。
Node-RED で change ノードや switch ノードの値の選択で「JSONata式」がありますが、この処理を実装しているのがこのライブラリのようですね。
まずは GitHub 上の README.md をざっと訳して、試してみましょう。2018年11月15日に version 1.5.4 を参照しています。
README.md of jsonata
クエリと変換のための言語 JSONata の JavaScript 実装です。
インストール
npm install jsonata
使い方
Node.js の場合:
var jsonata = require("jsonata");
var data = {
example: [
{value: 4},
{value: 7},
{value: 13}
]
};
var expression = jsonata("$sum(example.value)");
var result = expression.evaluate(data); // 24 を返す
【訳注】上記の例は地味なんですが、JSONata 式でワイルドカード * などが使えて、最後の部分を以下のように変えても同じ動作をするって楽しくないですか?
var expression = jsonata("$sum(*.value)");
var result = expression.evaluate(data); // これも 24 を返す
Web の場合:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONata test</title>
<script src="lib/jsonata.js"></script>
<script>
function greeting() {
var json = JSON.parse(document.getElementById('json').value);
var result = jsonata('"Hello, " & name').evaluate(json);
document.getElementById('greeting').innerHTML = result;
}
</script>
</head>
<body>
<textarea id="json">{ "name": "Wilbur" }</textarea>
<button onclick="greeting()">Click me</button>
<p id="greeting"></p>
</body>
</html>
【訳注】上記の html をブラウザ表示したのが以下で、「Click me」ボタンを押すと下にメッセージが表示されます。
jsonataは、generators などの ES2015 機能を使用します。 これらの機能がないブラウザでは、lib/jsonata-es5
を使用してください。
API
jsonata(str)
文字列 str
を JSONata 式として解析し、コンパイルされた JSONata expression オブジェクトを返します。
var expression = jsonata("$sum(example.value)");
式が有効な JSONata でない場合、構文エラーに関する情報を含む Error
がスローされます。たとえば、次のような:
{
code: "S0202",
stack: "...",
position: 16,
token: "}",
value: "]",
message: "Syntax error: expected ']' got '}'"
}
expression
には3つのメソッドがあります:
expression.evaluate(input[, bindings[, callback]])
オブジェクト input
に対してコンパイルされた JSONata expression を実行し、その結果を新しいオブジェクトとして返します。
var result = expression.evaluate({example: [{value: 4}, {value: 7}, {value: 13}]});
// 変数 result には 24 が代入される
input
は JSON.parse()
から返されるような JavaScript 値でなければなりません。input
が JSON 文字列として解析できなかった(循環、関数を含む、など)場合、expression
の動作は定義されていません。result
は JSON.stringify()
が正しく解釈できる JavaScript 値です。
bindings
は、存在する場合、バインドされる変数名と値(関数を含む)を含みます:
jsonata("$a + $b()").evaluate({}, {a: 4, b: () => 78});
// 82 を返す
expression.evaluate()
は実行時エラーをスローする可能性があります:
var expression = jsonata("$notafunction()"); // JSONata の書式としては問題ない
expression.evaluate({}); // エラーをスローする
Error
には、ランタイムエラーの情報が含まれています。たとえば、次のようになります:
{
code: "T1006",
stack: "...",
position: 14,
token: "notafunction",
message: "Attempted to invoke a non-function"
}
callback(err, value)
関数が指定されている場合、expression.evaluate()
は undefined
を返し、expression は非同期に実行され、Error
または結果が callback
関数に渡されます。
jsonata("7 + 12").evaluate({}, {}, (error, result) => {
if(error) {
console.error(error);
return;
}
console.log("Finished with", result);
});
console.log("Started");
// "Started" と表示され、次に "Finished with 19" と表示される
xpression.assign(name, value)
さきほどの例で bindings
が動作する方法と同様に、expression 内の名前に値を永続的にバインドします。expression 変更し undefined
を返します。JSONata expression ファクトリを作成する際に便利です。
var expression = jsonata("$a + $b()");
expression.assign("a", 4);
expression.assign("b", () => 1);
expression.evaluate({}); // 5
expression.evaluate()
を呼び出す際、bindings
引数が次の値を拘束することに注意してください:
expression.evaluate({}, {a: 109}); // 110
【訳注】上記の式はその前で定義した expression を使いまわしているので、jsonata("$a + $b()")
という JSONata 定義と、assign("b", () => 1)
という値定義の影響が残っている、ということでしょうかね。
expression.registerFunction(name, implementation[, signature])
expression 内の名前に関数を永続的にバインドします。
var expression = jsonata("$greet()");
expression.registerFunction("greet", () => "Hello world");
expression.evaluate({}); // "Hello world"
これは expression.assign
または expression.evaluate
の bindings
を使用して行うことができますが、expression.registerFunction
では関数 signature
を指定できます。これは予想される入力引数の型と、関数の戻り値の型を JSONata に伝える簡潔な文字列です。 JSONata は、実際の入力引数型が一致しない場合(戻り値の型はまだチェックされていない)、実行時エラーを発生させます。
var expression = jsonata("$add(61, 10005)");
expression.registerFunction("add", (a, b) => a + b, "<nn:n>");
expression.evaluate({}); // 10066
【訳注】上記の <nn:n>
という signatures を <n:n>
と変更して実行してみたところ、確かに Error がスローされるのが確認できました。
関数 signatures は次のように指定されます:
関数 signature 構文
関数 signature は <params:return>
という形式の文字列です。params
は型シンボルを順に並べたものであり、それぞれが入力引数の型を表します。return
は戻り値の型を表す単一の型シンボルです。
型の記号は次のように機能します:
シンプルな型:
-
b
- Boolean (真偽値) -
n
- number (数値) -
s
- string (文字列) -
l
-null
複雑な型:
-
a
- array (配列) -
o
- object (オブジェクト) -
f
- function (関数)
ユニオン型:
-
(sao)
- 文字列、配列、オブジェクトのいずれか -
(o)
-o
と同じ -
u
-(bnsl)
と同等 -
(bnsl)
- 真偽値、数値、文字列、null
のいずれか -
j
- 任意の JSON 型で、(bnsloa)
と同等 -
(bnsloa)
- 真偽値、数値、文字列、null
、オブジェクト、配列のいずれか、ただし関数は含まない -
x
- 任意の型で、(bnsloaf)
と同等
パラメータ型:
-
a<s>
- 文字列の配列 -
a<x>
- 任意の型の配列
組込み JSONata 関数の signature の例をいくつか示します:
-
$count
の signature は<a:n>
で、配列を受け取り、数値を返す -
$append
の signature は<aa:a>
で、2つの配列を受け取り、配列を返す -
$sum
の signature は<a<n>:n>
で、数値の配列を受け取り、数値を返す -
$reduce
v<fa<j>:j>
で、reducer 関数のf
と JSON オブジェクトの配列a<j>
を受け取り、JSON オブジェクトを返す
それぞれの型の記号には、オプションが適用されている場合もあります。
-
+
- この型の引数が1つ以上- 例)
$zip
の signature は<a+>
で、配列、もしくは2つの配列、もしくは3つの配列…(以下略) を受け取る
- 例)
-
?
- オプション(省略可能)の引数- 例)
$join
の signature は<a<s>s?:s>
で、文字列の配列と、そして接続に使用する文字列(デフォルト値は空)を受け取り、文字列を返す
- 例)
-
-
- この引数がない場合は、コンテキスト値("focus")を使用します- 例)
$length
の signature は<s-:n>
で、これは1つの引数を指定して$length(OrderID)
のように呼び出せますが、これはOrderID.$length()
と同じです
- 例)
More information
Contributing
このリポジトリに貢献する方法の詳細については、CONTRIBUTING.md を参照してください。
というわけで
JSONata なかなか面白いですね。使える 関数 の数も多いようで、JSON 形式のデータを扱う Node-RED フローで function ノードの数を激減できそうな気がしてきます。
JSONata の言語ガイド や 良い説明ページ を訳してみたので、こちらも参考にしてみてください。
いろいろ試してみたいとおもいます!また何か面白そうな情報がありましたら紹介しますね。
ではでは。