Node-RED の change/switch ノードで使える JSONata の README.md を訳してみた

Node-RED の [ソース](https://github.com/node-red/node-red/blob/master/package.json#L57) を眺めていたところ [JSONata](http://jsonata.org/) ライブラリを見つけました。JSON を操作する簡易言語のようで、なかなか面白いです。

Node-RED で change ノードや switch ノードの値の選択で「JSONata式」がありますが、この処理を実装しているのがこのライブラリのようですね。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/1c5878b9-64fc-879f-2c38-39d91b064346.png)

まずは GitHub 上の [README.md](https://github.com/jsonata-js/jsonata) をざっと訳して、試してみましょう。2018年11月15日に version 1.5.4 を参照しています。

# README.md of jsonata

クエリと変換のための言語 JSONata の JavaScript 実装です。

* [JSONataチュートリアル(英語)](https://github.com/jsonata-js/jsonata/blob/master/tutorial.md)
* [JSONata言語のドキュメント(英語)](http://docs.jsonata.org/)
* [やってみよう！(英語のツール)](http://try.jsonata.org/)

## インストール

```
npm install jsonata
```

## 使い方

Node.js の場合:

```javascript
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 式でワイルドカード * などが使えて、最後の部分を以下のように変えても同じ動作をするって楽しくないですか？

```javascript
var expression = jsonata("$sum(*.value)");
var result = expression.evaluate(data);  // これも 24 を返す
```

Web の場合:

```html
<!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」ボタンを押すと下にメッセージが表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/b08e2180-f660-747a-0851-7c4e055415be.png)

jsonataは、generators などの ES2015 機能を使用します。 これらの機能がないブラウザでは、``lib/jsonata-es5`` を使用してください。

## API

### jsonata(str)

文字列 `str` を JSONata 式として解析し、コンパイルされた JSONata expression オブジェクトを返します。

```javascript
var expression = jsonata("$sum(example.value)");
```

式が有効な JSONata でない場合、構文エラーに関する情報を含む `Error` がスローされます。たとえば、次のような:

```json
{
  code: "S0202",
  stack: "...",
  position: 16,
  token: "}",
  value: "]",
  message: "Syntax error: expected ']' got '}'"
}
```

`expression` には3つのメソッドがあります:

**expression.evaluate(input[, bindings[, callback]])**

オブジェクト `input` に対してコンパイルされた JSONata expression を実行し、その結果を新しいオブジェクトとして返します。

```javascript
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` は、存在する場合、バインドされる変数名と値(関数を含む)を含みます:

```javascript
jsonata("$a + $b()").evaluate({}, {a: 4, b: () => 78});
// 82 を返す
```

`expression.evaluate()` は実行時エラーをスローする可能性があります:

```javascript
var expression = jsonata("$notafunction()"); // JSONata の書式としては問題ない
expression.evaluate({}); // エラーをスローする
```

`Error` には、ランタイムエラーの情報が含まれています。たとえば、次のようになります:

```json
{
  code: "T1006",
  stack: "...",
  position: 14,
  token: "notafunction",
  message: "Attempted to invoke a non-function"
}
```

`callback(err, value)` 関数が指定されている場合、`expression.evaluate()` は `undefined` を返し、expression は非同期に実行され、`Error` または結果が `callback` 関数に渡されます。

```javascript
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 ファクトリを作成する際に便利です。

```javascript
var expression = jsonata("$a + $b()");
expression.assign("a", 4);
expression.assign("b", () => 1);

expression.evaluate({}); // 5
```

`expression.evaluate()` を呼び出す際、`bindings` 引数が次の値を拘束することに注意してください:

```javascript
expression.evaluate({}, {a: 109}); // 110
```

【訳注】上記の式はその前で定義した expression を使いまわしているので、`jsonata("$a + $b()")` という JSONata 定義と、`assign("b", () => 1)` という値定義の影響が残っている、ということでしょうかね。

**expression.registerFunction(name, implementation[, signature])**

expression 内の名前に関数を永続的にバインドします。

```javascript
var expression = jsonata("$greet()");
expression.registerFunction("greet", () => "Hello world");

expression.evaluate({}); // "Hello world"
```

これは `expression.assign` または `expression.evaluate` の `bindings` を使用して行うことができますが、`expression.registerFunction` では関数 `signature` を指定できます。これは予想される入力引数の型と、関数の戻り値の型を JSONata に伝える簡潔な文字列です。 JSONata は、実際の入力引数型が一致しない場合(戻り値の型はまだチェックされていない)、実行時エラーを発生させます。

```javascript
var expression = jsonata("$add(61, 10005)");
expression.registerFunction("add", (a, b) => a + b, "<nn:n>");

expression.evaluate({}); // 10066
```

【訳注】上記の `<nn:n>` という signatures を `<n:n>` と変更して実行してみたところ、確かに Error がスローされるのが確認できました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/4b1a1127-edcb-f1e8-235b-ea05bc1cc083.png)

関数 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
` - 文字列の配列
* `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>` で、文字列の配列と、そして接続に使用する文字列(デフォルト値は空)を受け取り、文字列を返す
* `-` - この引数がない場合は、コンテキスト値("focus")を使用します
  * 例) `$length` の signature は `<s-:n>` で、これは1つの引数を指定して `$length(OrderID)` のように呼び出せますが、これは `OrderID.$length()` と同じです

## More information

* [JSONata 言語のドキュメント (英語)](http://docs.jsonata.org/)
* [JSONata 技術トーク (英語)](https://developer.ibm.com/open/videos/dw-open-tech-talk-jsonata/)

## Contributing

このリポジトリに貢献する方法の詳細については、[CONTRIBUTING.md](https://github.com/jsonata-js/jsonata/blob/master/CONTRIBUTING.md) を参照してください。

# というわけで

JSONata なかなか面白いですね。使える [関数](http://docs.jsonata.org/string-functions.html) の数も多いようで、JSON 形式のデータを扱う Node-RED フローで function ノードの数を激減できそうな気がしてきます。

[JSONata の言語ガイド](https://qiita.com/yamachan360/items/91805334890d18ef6e23) や [良い説明ページ](https://qiita.com/yamachan360/items/3c0ebb0a246da1000975) を訳してみたので、こちらも参考にしてみてください。

いろいろ試してみたいとおもいます！また何か面白そうな情報がありましたら紹介しますね。

ではでは。