[JSONata] Object Transform Operatorを理解しよう
こんにちは。サービス開発室の武田です。
引き続き、JSONataを学んでいきましょう。本日は Object Transform Operator です。
Object Transform Operatorはオブジェクトのプロパティを更新したり削除するために使用します。日本語訳は見当たりませんでしたが、そのまま「オブジェクト変換演算子」なんでしょうか。長いので、このエントリでは「変換演算子」と呼ぶことにします。
ドキュメントを見ただけではちょっとわかりにくいので、実際に動作を確認しながら見ていきましょう。
要するに関数リテラル
ドキュメントには... ~> | ... | ... | (Transform)
というように記載されています。ぱっと見ではよくわかりませんが、このエントリのゴールはこれが何を言っているのかを理解することです。
まず先に本質の理解をしてしまいましょう。ズバリ| ... | ... |
は特殊な 関数リテラル です。/.../...
が 正規表現リテラル であると同じ話で、| ... | ... |
は関数リテラルなのです。順を追って見ていきます。
関数リテラルはプログラミング言語などでは「ラムダ式」とも呼ぶことがありますが、JSONataでは通常、次のように書けます。
(
$f := function($a) { $a + 1 };
$f(1) /* 2 */
)
function($a) { $a + 1 }
の部分が関数リテラルですね。余談になりますが、JavaScriptではアロー演算子(=>
)を使用できます。JSONataではこれが使えない代わり(?)に、次のようなオシャレな書き方ができたりします。
(
$f := λ($a) { $a + 1 }; /* ←オシャレポイント */
$f(1) /* 2 */
)
話を戻しましょう。変換演算子も先ほどの例と似たような使い方ができます。たとえば次の例を見てみましょう。
(
$f := | $ | {"age": age + 1} |;
$f({"age": 17}) /* { "age": 18 } */
)
一度$f
に代入し、次の行で引数を与えて呼び出していますね?最初の例とそっくりです。これが何を意味しているかと言えば、| $ | {"age": age + 1} |
は呼び出し可能な無名関数を定義していて、変数にも代入できるということです。そのため次のように直接呼び出すことも可能です。
| $ | {"age": age + 1} |({"age": 17})
さらに、引数を与えて呼び出せるということは、チェインオペレーター(~>
)を使用した書き換えが可能ということになります。
{"age": 17} ~> | $ | {"age": age + 1} |
これで、今回の疑問の出発点だった... ~> | ... | ... |
が 何者なのか わかりましたね。無名関数を定義してそれを呼び出しているだけだったのです。
変換演算子が行う処理はなんなのか
先ほどの話で、変換演算子が何者なのかはわかりました。次は 何をする ためのものなのか見ていきましょう。わざわざfunction() {}
以外の無名関数の定義方法を用意するくらいですので、何か便利な点があるわけです。
変換演算子で宣言された無名関数は次のような特徴があります。
- 引数に対して非破壊的な変更をする
- プロパティの更新や削除ができる
非破壊的な変更 は重要なポイントです。つまり元のオブジェクトを(内部的に)コピーし、そのコピーに対して変換処理を行います。そのため元のオブジェクトは、変更は行われずそのまま残ります。また変換演算子が通常の関数と違う点が2つ目で、使用する目的はプロパティの 更新や削除 となります。
変換演算子は次の書式です。
| location | update [, delete] |
location
は変換する対象を指定します。update
にはオブジェクトを指定し、location
で指定されたプロパティの値とのマージ処理が行われます。delete
はオプションとなっており、削除したいプロパティを文字列または文字列配列で指定します。
具体例を交えながら見ていきましょう。
プロパティの追加
プロパティを追加したい場合は、update
に追加したいプロパティを持ったオブジェクトを指定します。
(
$a := {"a": 1, "b": 2, "c": {"ns": [1]}};
$a ~> | $ | {"d": 10} |
)
結果はこうなります。
{
"a": 1,
"b": 2,
"c": {
"ns": [1]
},
"d": 10
}
プロパティの更新
プロパティを更新したい場合は、update
に更新したいプロパティと更新処理を持ったオブジェクトを指定します。
(
$a := {"a": 1, "b": 2, "c": {"ns": [1]}};
$a ~> | $ | {"b": b * 10 } |
)
結果はこうなります。
{
"a": 1,
"b": 20,
"c": {
"ns": [1]
}
}
プロパティの削除
プロパティを削除したい場合は、delete
に削除したいプロパティを文字列または文字列配列で指定します。update
は省略できないので、単に削除したい場合は空のオブジェクト({}
)を指定すればOKです。
(
$a := {"a": 1, "b": 2, "c": {"ns": [1]}};
$a ~> | $ | {} , "c" |
)
結果はこうなります。
{
"a": 1,
"b": 2
}
複数削除したい場合は、配列で指定します。
(
$a := {"a": 1, "b": 2, "c": {"ns": [1]}};
$a ~> | $ | {} , ["a", "c"] |
)
結果はこうなります。
{
"b": 2
}
locationの指定
location
の指定をずっと$
でしてきましたが、これを指定することで、処理をその部分のみに絞れます。たとえば次の例では$a.c
を対象にした処理をしています。
(
$a := {"a": 1, "b": 2, "c": {"ns": [1]}};
$a ~> | c | {"ns": $append(ns, [2, 3, 4])} |
)
結果はこうなります。
{
"a": 1,
"b": 2,
"c": {
"ns": [
1,
2,
3,
4
]
}
}
まとめ
変換演算子について理解できたでしょうか。最初はとっつきにくいのですが、実際に動かしてみることで少しずつ理解できてきますね。最後にクイズを出して終わりにしましょう。どのような結果になるのか、考えてみてください。
(
$a := {"a": 1, "b": 2, "c": {"ns": [1]}};
$b := {"b": 20};
$merge([$a, $b]) = ($a ~> | $ | $b |)
)
結果はこちら。