AWS StepFunctionsのクエリ言語JSONataの解説動画 JSONata in 5 minutesをやってみた

## はじめに
2024年11月、AWS Step Functionsで新たにJSONataというクエリ言語が使用可能になりました。
https://aws.amazon.com/jp/about-aws/whats-new/2024/11/aws-step-functions-variables-jsonata-transformations/

現在はステートマシン作成時に既存のJSONPathとJSONataどちらも選べるようになっています。

しかし、新規作成されるステートマシンではこれまでのクエリ言語JSONPathではなくJSONataがデフォルト選択されるようになっており、AWSとしてもJSONataを推奨しているようです。

AWS StepFunctionsのコンソールで試せるHello WorldテンプレートでもJSONataが使用されています。

https://qiita.com/kenpi1313/items/f3b198b9baad8e4af50f

JSONataについては公式サイトトップに要点がまとめてあり、
https://jsonata.org/

![スクリーンショット 2024-12-29 17.41.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415169/abd65adf-6a96-3aeb-0b11-e03f4ced993a.png)

- Lightweight query and transformation language for JSON data
- Inspired by the location path semantics of XPath 3.1
- Sophisticated query expressions with minimal syntax
- Built in operators and functions for manipulating and combining data
- Create user-defined functions
- Format query results into any JSON output structure

JSONデータを対象に、演算や結合・関数を用いてクエリやデータ変換を行うことができる言語のようです。

公式サイトトップの解説動画 JSONata in 5 minutesで基本要素を掴むことができます。
動画が英語版だったこともあり内容の要点を本記事にまとめてみました。

https://www.youtube.com/watch?v=ZBaK40rtIBM

## 1 Play Groundを開く
下記URLでJSONataの練習を行うことができます。


https://try.jsonata.org/

開くと下記のようなページが表示されます。

![スクリーンショット 2024-12-29 17.50.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415169/9fa98df8-a8e1-76f5-3fb9-dd877a8e7acb.png)

赤線で分けている部分が今回扱うエリアとなります。
- 左側全体が「基となるJSONデータが表示されているエリア」
- 左側の右上に「データのテーマを切り替えることができるプルダウン」
- 右上が「クエリ言語を自由に入力できるエリア」
- 右下が「クエリを通したあとの出力エリア」

になっています。プルダウンで切り替えられるテーマは現在
- Invoice
- Address
- Schema
- Library
- Bindings

となっています。動画の中ではAddressとInvoiceが扱われています。

## 実際に手を動かしてみる

プルダウンで`Address`を選択します。

![スクリーンショット 2024-12-29 18.03.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415169/3c9c0ba0-1316-9f52-d2f8-f8d29551521d.png)

右上に例のサンプルクエリ

```
{
  "name": FirstName & " " & Surname,
  "mobile": Phone[type = "mobile"].number
}
```

が表示されていますが、一旦削除して、そこに`Surname`と入力してみます。

すると、下記の文字列が出力されます。

```
"Smith"
```

このように元データのJSONのKeyである`Surname`をクエリ言語に設定すると、そのValueである文字列が出力できるようです。


ここまでは簡単ですね。

次は別のデータ種別を参照してみましょう。
右上のエリアに`Address`と入力します。すると、下記のようにオブジェクトが出力されます。
```
{
  "Street": "Hursley Park",
  "City": "Winchester",
  "Postcode": "SO21 2JN"
}
```

ここまではJSONデータの直下のKeyに対する参照でしたが、一つ階層が深い、`City`を参照したい場合について試してみます。

`Address.City`とKeyをドットで繋いでクエリを入力してみます。
すると下記のようにAddress→Cityの階層のValueが出力できます。

```
"Winchester"
```

次に、文字列要素同士の結合を試します。
入力欄に`FirstName & ' ' & Surname`と入力してみます。すると下記のようにFirstName、半角スペース、Surnameの形で出力されます。

```
"Fred Smith"
```
:::note info
`&`演算子を使って結合ができることが分かりました。
:::

:::note warn
動画では出てきませんが、よりイメージが膨らむよう下記のように`-`でつなげてみましょう
`FirstName & '-' & Surname`を入力します。
すると下記のように`-`つなぎで出力されることが分かります。
```
"Fred-Smith"
```
:::

次に、`Phone`を入力すると、オブジェクトの配列が取れます。

```
[
  {
    "type": "home",
    "number": "0203 544 1234"
  },
  {
    "type": "office",
    "number": "01962 001234"
  },
  {
    "type": "office",
    "number": "01962 001235"
  },
  {
    "type": "mobile",
    "number": "077 7700 1234"
  }
]
```

`Phone.number`と入力すると、それぞれのオブジェクトのnumberを抽出し、文字列の配列として出力します。

```
[
  "0203 544 1234",
  "01962 001234",
  "01962 001235",
  "077 7700 1234"
]
```

:::note warn
動画には出てきませんが、同じ原理で`Phone.type`と入力してみると下記のように出力されます。
```
[
  "home",
  "office",
  "office",
  "mobile"
]
```
:::

JavaScriptのmap処理と同じ動きをしています。

```js
const Phone = [
  { type: "home", number: "0203 544 1234" },
  { type: "office", number: "01962 001234" },
  { type: "office", number: "01962 001235" },
  { type: "mobile", number: "077 7700 1234" }
];

// すべての電話番号を取得
const numbers = Phone.map(phone => phone.number);
console.log(numbers); 
// ["0203 544 1234", "01962 001234", "01962 001235", "077 7700 1234"]
```

配列の参照

`Phone[0].number`と入力すると下記文字列が出力されます。配列の特定の要素には`[i]`（iは任意の数値）でアクセスできるようです。

```
"0203 544 1234"
```

`type`を使用したフィルタリングもできます。

`Phone[type='mobile'].number`と入力すると、下記が出力されます。

```
"077 7700 1234"
```
`Phone`の中で唯一typeが`mobile`だったnumberの値を抽出できていることが分かります。
```
    {
      "type": "mobile",
      "number": "077 7700 1234"
    }
```

:::note info
`=`演算子を用いた比較ができることが分かりました。
:::

https://docs.jsonata.org/expressions

公式サイトには`comparison-expressions`として紹介されています。

動画ではここでInvoiceテーマのJSONデータに切り替えているので、左エリア右上のプルダウンで`Invoice`を選択し、データの切り替えを行いましょう。

`Account.Order.Product.Price`と入力してみます。すると、下記のように数値の配列が出力されます。

```
[
  34.45,
  21.67,
  34.45,
  107.99
]
```

商品の価格が出力されましたが、そこに数をかけ合わせてそれぞれの商品の合計金額を出力します。
`Account.Order.Product.(Price * Quantity)`と入力してみましょう。

すると下記のように`Price * Quantity`された数が配列として出力されています。

```
[
  68.9,
  21.67,
  137.8,
  107.99
]
```

こちらもJavaScriptのmapを使うと下記のように表現できます。

```js
const Order =  [
  { Product: { Price: 34.45, Quantity: 2 } },
  { Product: { Price: 21.67, Quantity: 1 } },
];

const totalPrices = Order.map(order => {
  return order.Product.Price * order.Product.Quantity;
});

console.log(totalPrices);
// [68.9,21.67]
```

次は商品価格と商品数を踏まえた総額を出力します。
`$sum(Account.Order.Product.(Price * Quantity))`と入力します。
すると下記のように総額が出力されます。

```
336.36
```

:::note info
`$sum`関数を使った演算ができることが分かりました。
:::

この`$sum()`は`aggregation-functions`として定義されているJSONataの関数です。

https://docs.jsonata.org/aggregation-functions

他にも`max`,`min`,`average`などを使用することができます。

:::note warn
動画には出てきませんが
`$max(Account.Order.Product.(Price * Quantity))`と入力してみましょう。
すると、
`Account.Order.Product.(Price * Quantity)`で出力された

```
[
  68.9,
  21.67,
  137.8,
  107.99
]
```
の中の最大値である
```
137.8
```

が1件出力されます。
:::

お疲れ様でした。ここで動画は終わっています。

## 終わりに

5分でJSONataについて簡単に試せる動画となっており、JSONataの感覚を掴むには最適なコンテンツでした。
様々な演算子や関数を使うことができるため、AWS StepFunctionsのみで値の計算を完結できるようになる気がします（今まではLambda等で加工していた）。

更にJSONataを使いこなすために、このPlayGroundで遊ぶのも良し、[公式ドキュメント](https://docs.jsonata.org/overview)の読み込みや、Step Functionsのコンソール上で試してみるのも良さそうです！