AWS

Step FunctionsのJSONataとJSONPathの違いを理解するハンズオンを考えてみた

kim

おはようございます。DWSのkimです!

昨年の2024年11月22日にStep FunctionsでJSONataが使えるようになったという発表がありました。

導入されたからこの一年、あちこちで「JSONataが導入されて、Step Functionsが以前にも増して便利になった!」という声を聞くようになりました。

そこで今回は、Step Functionsでこれまで使われていたJSONPathと比べて、JSONataがどれくらい便利になったのかをまとめました。ハンズオン形式で紹介していきたいと思います!

JSONataとは?

JSONataは、JSONデータの抽出、変換、操作を行うための専用のクエリ・変換言語です。

以前からStep FunctionsではJSONPathが使われていますが、JSONPathでは、値の抽出はできるものの、入力されたJSONを少しでも加工しようと考えると、Lambdaなどの他のサービスと組み合わせないと、フローが複雑になる傾向がありました。

今回、JSONataが導入されたことで、このデータ加工をStep Functions内で簡単にできるようになり、ワークフローのステップの複雑さがかなり軽減されるようになりました。

下記はAWS公式ページで掲載されている。Step FunctionsでJSONPathを用いた場合とJSONataを用いた場合の比較図です。この図からもフローの複雑性が軽減されることがわかります。

https://docs.aws.amazon.com/ja_jp/step-functions/latest/dg/transforming-data.html

それでは次項から、ハンズオンを通じて両者の違いを実際に体験していきましょう!

比較1:基本的なデータ抽出を行う場合

まずは、単純なキー参照・値の抽出で、JSONPath / JSONataそれぞれの基本的な違いを見ていきたいと思います。

下記、Step Functionsへの入力データから、想定出力結果を得るために、JSONPath、JSONataそれぞれでどのようなStep Functionsを作れば、いいかまず考えてみてください!

  • 入力データ
{
  "customer": {
    "id": "C123",
    "name": "Alice",
    "orders": [
      {"orderId": "O100", "amount": 250},
      {"orderId": "O101", "amount": 175}
    ]
  }
}
  • 想定出力結果
{
  "customerId": "C123",
  "customerName": "Alice"
}

  • JSONPathの場合
{
  "Comment": "基本データ抽出-JSONPath",
  "QueryLanguage": "JSONPath",
  "StartAt": "ExtractCustomer",
  "States": {
    "ExtractCustomer": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customer.id",
        "customerName.$": "$.customer.name"
      },
      "End": true
    }
  }
}
  • JSONataの場合
{
  "Comment": "基本データ抽出-JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "ExtractCustomer",
  "States": {
    "ExtractCustomer": {
      "Type": "Pass",
      "Output": {
        "customerId": "{% $states.input.customer.id %}",
        "customerName": "{% $states.input.customer.name %}"
      },
      "End": true
    }
  }
}

値の取り出しだけなら、記述量、ステップ数はそれほど違いはなく、記法が少し変わるだけになります。違いは下記にまとめましたので、ご参照ください。

項目JSONPath モードJSONata モード
値部分$ から始まる JSONPath 構文。
例:$.customer.id
{% <JSONata 式> %} の形式で埋め込み。
例:"{% $states.input.customer.id %}"
キー部分.<key>.$ という形を使う
(例:"name.$": "$.customer.id")。
「. $」をキー名につけることなく、通常のキー名で "name": "{% … %}" のように記載。
変数参照/予約変数 $.customer.id のように直接値を取り出す。 JSONata モードでは予約変数が使える。(例:$states.input$states.result
利用可能なフィールド構成InputPathParametersResultSelectorResultPathOutputPath など、複数フィールドで JSONPath のパスを指定。 JSONata モードでは ArgumentsOutput フィールドが入り口となる。
参考記事AWS ドキュメント:Using JSONPath paths
AWS ドキュメント:Step Functions での JSONata によるデータの変換

比較2:配列の変換とフィルタリング

次に、配列データを変換/フィルタリングし、その結果を出力してみましょう!

こちらも先ほどと同様に下記、Step Functionsへの入力データから、想定出力結果を得るためのフローを考えていただきたいです。ただ、今回作成するフローは、JSONataだけでOKです。JSONPathは少し複雑になりますし、色々な制約があるので、チャレンジしてみたい方だけやってください!

※JSONPathでの記載にチャレンジする場合、JSONPathだけだと配列の数が動的に変わる場合、対応できません、JSONPathの作成の際は、配列の数は固定値として対応してください。

  • 入力データ
{
  "customer": {
    "id": "C123",
    "orders": [
      {"orderId": "O100", "amount": 250, "status": "SHIPPED"},
      {"orderId": "O101", "amount": 175, "status": "PENDING"},
      {"orderId": "O102", "amount": 300, "status": "SHIPPED"}
    ]
  }
}
  • 想定出力結果
{
  "shippedOrderIds": ["O100", "O102"],
  "totalShippedAmount": 550
}
  • JSONPath
コード


{
  "Comment": "JSONPath-配列の変換とフィルタリング",
  "QueryLanguage": "JSONPath",
  "StartAt": "InitializeTotal",
  "States": {
    "InitializeTotal": {
      "Type": "Pass",
      "Parameters": {
        "shippedOrders.$": "$.customer.orders[?(@.status=='SHIPPED')]",
        "amounts.$": "$.customer.orders[?(@.status=='SHIPPED')].amount",
        "totalAmount": 0
      },
      "Next": "AddAmount1"
    },
    "AddAmount1": {
      "Type": "Pass",
      "Parameters": {
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount.$": "States.MathAdd($.totalAmount, $.amounts[0])"
      },
      "Next": "CheckAmount2"
    },
    "CheckAmount2": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.amounts[1]",
          "IsPresent": true,
          "Next": "AddAmount2"
        }
      ],
      "Default": "CheckAmount3"
    },
    "AddAmount2": {
      "Type": "Pass",
      "Parameters": {
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount.$": "States.MathAdd($.totalAmount, $.amounts[1])"
      },
      "Next": "CheckAmount3"
    },
    "CheckAmount3": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.amounts[2]",
          "IsPresent": true,
          "Next": "AddAmount3"
        }
      ],
      "Default": "TransformShipped"
    },
    "AddAmount3": {
      "Type": "Pass",
      "Parameters": {
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount.$": "States.MathAdd($.totalAmount, $.amounts[2])"
      },
      "Next": "TransformShipped"
    },
    "TransformShipped": {
      "Type": "Pass",
      "Parameters": {
        "shippedOrderIds.$": "$.shippedOrders[*].orderId",
        "totalShippedAmount.$": "$.totalAmount"
      },
      "End": true
    }
  }
}

他にも方法があるかもしれませんが、私は上記のように記載しました。コードは少し長くなるので、「▶︎コード」をクリックしてご確認ください。

  • JSONata
{
  "Comment": "配列の変換とフィルタリング – JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "TransformShipped",
  "States": {
    "TransformShipped": {
      "Type": "Pass",
      "Output": {
        "shippedOrderIds": "{% $states.input.customer.orders[status='SHIPPED'].orderId %}",
        "totalShippedAmount": "{% $sum($states.input.customer.orders[status='SHIPPED'].amount) %}"
      },
      "End": true
    }
  }
}

JSONataだとJSONPathと比べて、明らかにステップ数と記述するコードの量が変わりました。

違いとしては、JSONPathではStates.MathAdd、JSONataではsumを使って足し算を行っています。これらの違いは下記表をご参照ください。

項目States.MathAdd
(JSONPath)
sum
(JSONata モード)
用途2つの整数を加算するため。例えば States.MathAdd($.value1, $.step) のように使う。
(参考:AWS ドキュメント)
配列の数値をすべて足し合わせて合計を返す。例: $sum([5,1,3,7,4]) => 20
(参考:docs.jsonata.org)

States.MathAddだと、2つの整数の足し算しかできないので、配列の数だけ、毎回States.MathAddを呼び出して足し算を行う必要があり、その分ステップ数が多くなります。

また、配列の中身の有無に応じて自動で足し算を終了してくれるわけではないので、配列の数が動的に変わる場合は、Choice Stateなどを用いて、配列の中身の有る無しを判定させて終了するような処理を入れる必要があります。

これらの処理が発生することからJSONPath単体でStep Functionsを構成しようとすると、ステップ数の多い、複雑なフローを作らざるを得なくなります。

比較3:条件分岐と文字列操作

次は、条件分岐と文字列操作になります!

こちらも同様に下記、Step Functionsへの入力データから、想定出力結果を得るためのフローを考えてください!条件分岐に関しては、JSONPathでも条件分岐用のフローが存在するので、簡単に作成可能です!

  • 入力データ
{
  "customer": {
    "id": "C123",
    "name": "Alice",
    "lastOrder": {
      "orderId": "O102",
      "amount": 300,
      "status": "SHIPPED"
    }
  },
  "threshold": 200
}
  • 想定出力結果
{
  "customerSummary": "Customer Alice (ID: C123) placed an order O102 of amount 300 which is above threshold.",
  "isHighValueCustomer": true
}
  • JSONPath
{
  "StartAt": "EvaluateCustomer",
  "States": {
    "EvaluateCustomer": {
      "Type": "Pass",
      "Parameters": {
        "name.$": "$.customer.name",
        "id.$": "$.customer.id",
        "orderId.$": "$.customer.lastOrder.orderId",
        "amount.$": "$.customer.lastOrder.amount",
        "threshold.$": "$.threshold"
      },
      "Next": "CheckHighValue"
    },
    "CheckHighValue": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.amount",
          "NumericGreaterThanEqualsPath": "$.threshold",
          "Next": "BuildAboveSummary"
        }
      ],
      "Default": "BuildBelowSummary"
    },
    "BuildAboveSummary": {
      "Type": "Pass",
      "Parameters": {
        "customerSummary.$": "States.Format('Customer {} (ID: {}) placed an order {} of amount {} which is above threshold.', $.name, $.id, $.orderId, $.amount)",
        "isHighValueCustomer": true
      },
      "End": true
    },
    "BuildBelowSummary": {
      "Type": "Pass",
      "Parameters": {
        "customerSummary.$": "States.Format('Customer {} (ID: {}) placed an order {} of amount {} which is below threshold.', $.name, $.id, $.orderId, $.amount)",
        "isHighValueCustomer": false
      },
      "End": true
    }
  }
}
  • JSONata
{
  "Comment": "条件分岐と文字列操作-JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "BuildSummary",
  "States": {
    "BuildSummary": {
      "Type": "Pass",
      "Output": {
        "customerSummary": "{% 'Customer ' & $states.input.customer.name & ' (ID: ' & $states.input.customer.id & ') placed an order ' & $states.input.customer.lastOrder.orderId & ' of amount ' & $states.input.customer.lastOrder.amount & ' which is ' & ($states.input.customer.lastOrder.amount >= $states.input.threshold ? 'above' : 'below') & ' threshold.' %}",
        "isHighValueCustomer": "{% $states.input.customer.lastOrder.amount >= $states.input.threshold %}"
      },
      "End": true
    }
  }
}

JSONPathでは、Choice Stateを用いて分岐させていますが、JSONataでは三項演算子や、比較演算子が使えるため、それらで分岐を行っています。今回の条件だとステップ数はそれほど遜色ないですが、コード量は明らかにJSONataの方が少なくなります。

項目JSONPath(Choice State)JSONata(式による条件分岐)
分岐の仕組みChoiceステートを使って明示的に条件を定義JSONata式で三項演算子比較演算子が使える
書き方の分かりやすさ分かりやすく構造化されているが、条件が複雑になると冗長になる複雑な条件や式を一つのJSONata 式で書けて柔軟に、ロジックとデータ変換をまとめて表現できる

比較4:集計とグループ化

配列データをカテゴリ別にグループ化して集計してみましょう!

今回も比較2と同様にJSONataだけやってみていただければOKです!JSONPathはかなり複雑になると思うので、「JSONPathで複雑だったフローが、JSONataだとこんな簡単に定義できるんだ」と見て、感じていただければOKです!

  • 入力データ
{
  "sales": [
    {"category": "home", "amount": 800000},
    {"category": "auto", "amount": 8000},
    {"category": "boat", "amount": 15000},
    {"category": "auto", "amount": 25000},
    {"category": "auto", "amount": 102000},
    {"category": "home", "amount": 500000}
  ]
}
  • 想定出力結果
{
  "summaryByCategory": [
    {"category": "home", "total": 1300000},
    {"category": "auto", "total": 135000},
    {"category": "boat", "total": 15000}
  ]
}
  • JSONPath
コード


{
  "Comment": "JSONPath-集計とグループ化",
  "QueryLanguage": "JSONPath",
  "StartAt": "ExtractCategories",
  "States": {
    "ExtractCategories": {
      "Type": "Pass",
      "Parameters": {
        "sales.$": "$.sales",
        "homeAmounts.$": "$.sales[?(@.category=='home')].amount",
        "autoAmounts.$": "$.sales[?(@.category=='auto')].amount",
        "boatAmounts.$": "$.sales[?(@.category=='boat')].amount"
      },
      "Next": "InitHomeTotal"
    },
    "InitHomeTotal": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "homeTotal": 0
      },
      "Next": "AddHomeAmount0"
    },
    "AddHomeAmount0": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "homeTotal.$": "States.MathAdd($.homeTotal, $.homeAmounts[0])"
      },
      "Next": "CheckHomeAmount1"
    },
    "CheckHomeAmount1": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.homeAmounts[1]",
          "IsPresent": true,
          "Next": "AddHomeAmount1"
        }
      ],
      "Default": "InitAutoTotal"
    },
    "AddHomeAmount1": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "homeTotal.$": "States.MathAdd($.homeTotal, $.homeAmounts[1])"
      },
      "Next": "InitAutoTotal"
    },
    "InitAutoTotal": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "homeTotal.$": "$.homeTotal",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "autoTotal": 0
      },
      "Next": "AddAutoAmount0"
    },
    "AddAutoAmount0": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "homeTotal.$": "$.homeTotal",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "autoTotal.$": "States.MathAdd($.autoTotal, $.autoAmounts[0])"
      },
      "Next": "CheckAutoAmount1"
    },
    "CheckAutoAmount1": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.autoAmounts[1]",
          "IsPresent": true,
          "Next": "AddAutoAmount1"
        }
      ],
      "Default": "InitBoatTotal"
    },
    "AddAutoAmount1": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "homeTotal.$": "$.homeTotal",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "autoTotal.$": "States.MathAdd($.autoTotal, $.autoAmounts[1])"
      },
      "Next": "CheckAutoAmount2"
    },
    "CheckAutoAmount2": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.autoAmounts[2]",
          "IsPresent": true,
          "Next": "AddAutoAmount2"
        }
      ],
      "Default": "InitBoatTotal"
    },
    "AddAutoAmount2": {
      "Type": "Pass",
      "Parameters": {
        "homeAmounts.$": "$.homeAmounts",
        "homeTotal.$": "$.homeTotal",
        "autoAmounts.$": "$.autoAmounts",
        "boatAmounts.$": "$.boatAmounts",
        "autoTotal.$": "States.MathAdd($.autoTotal, $.autoAmounts[2])"
      },
      "Next": "InitBoatTotal"
    },
    "InitBoatTotal": {
      "Type": "Pass",
      "Parameters": {
        "homeTotal.$": "$.homeTotal",
        "autoTotal.$": "$.autoTotal",
        "boatAmounts.$": "$.boatAmounts",
        "boatTotal": 0
      },
      "Next": "CheckBoatAmount0"
    },
    "CheckBoatAmount0": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.boatAmounts[0]",
          "IsPresent": true,
          "Next": "AddBoatAmount0"
        }
      ],
      "Default": "FinalizeResult"
    },
    "AddBoatAmount0": {
      "Type": "Pass",
      "Parameters": {
        "homeTotal.$": "$.homeTotal",
        "autoTotal.$": "$.autoTotal",
        "boatTotal.$": "States.MathAdd($.boatTotal, $.boatAmounts[0])"
      },
      "Next": "FinalizeResult"
    },
    "FinalizeResult": {
      "Type": "Pass",
      "Parameters": {
        "summaryByCategory": [
          {
            "category": "home",
            "total.$": "$.homeTotal"
          },
          {
            "category": "auto",
            "total.$": "$.autoTotal"
          },
          {
            "category": "boat",
            "total.$": "$.boatTotal"
          }
        ]
      },
      "End": true
    }
  }
}

今回もコードが少し長くなるので、「▶︎コード」にまとめました。クリックしてご確認ください。

  • JSONata
{
  "Comment": "集計とグループ化 – JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "ComputeAggregation",
  "States": {
    "ComputeAggregation": {
      "Type": "Pass",
      "Output": {
        "summaryByCategory": "{% $map($distinct($states.input.sales.category), function($cat){ { \"category\": $cat, \"total\": $sum($states.input.sales[category=$cat].amount) } }) %}"
      },
      "End": true
    }
  }
}

今回、JSONataの記法で新しく、$distinctと$mapを使いました。JSONataでは、プログラムで一般的に使われるこのような関数が使用可能です!今回使用した関数の使い方は下記にて記載させていただきます。

  • $distinct(...)
    • 重複を除去
    • 例: ["home", "auto", "boat"]
  • $map(配列, function($cat){ ... })
    • 各カテゴリに対して処理を実行し、結果の配列を返す

簡単に、JSONataの記述を解説すると$states.input.sales.category["home", "auto", "boat", "auto", "auto", "home"]になるので、$distinctで重複を削除して、各要素(["home", "auto", "boat"])だけを取り出します。

各要素を$mapを用いて、想定出力結果の配列と同じになるように整形して、出力しました。

その他にも、使用可能な関数は下記JSONataの公式ページをご参照ください!

JSONata Documentation(Array Functions)

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

今回もJSONPathより、JSONataの場合の方が、ステップ数も、コードの記述量もかなり差が出ました。JSONataが導入されたことで、Step Functionsがかなり便利になったことがわかりますね!

比較5:実践的なワークフロー

これまでの要素(抽出・変換・フィルタリング・条件分岐・集計)を組み合わせた、実践的なワークフローを作成してみましょう!

今回も比較2と同様にJSONataだけやってみていただければOKです!JSONPathがかなり複雑になるので!もちろん、チャレンジしていただいても大丈夫です!

  • 入力データ
{
  "customer": {
    "id": "C123",
    "name": "Alice",
    "orders": [
      {"orderId": "O100", "amount": 250, "status": "SHIPPED"},
      {"orderId": "O101", "amount": 175, "status": "PENDING"},
      {"orderId": "O102", "amount": 300, "status": "SHIPPED"}
    ]
  },
  "threshold": 200
}
  • 想定出力結果
{
  "customerId": "C123",
  "customerName": "Alice",
  "shippedOrderCount": 2,
  "shippedOrderIds": ["O100", "O102"],
  "totalShippedAmount": 550,
  "isHighValueCustomer": true,
  "report": "Customer Alice (ID: C123) has 2 shipped orders with total amount 550, which is above threshold 200."
}
  • JSONPath
コード


{
  "Comment": "実践ワークフロー – JSONPath",
  "QueryLanguage": "JSONPath",
  "StartAt": "ExtractBasics",
  "States": {
    "ExtractBasics": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customer.id",
        "customerName.$": "$.customer.name",
        "orders.$": "$.customer.orders",
        "threshold.$": "$.threshold"
      },
      "Next": "InitializeTotal"
    },
    "InitializeTotal": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount": 0
      },
      "Next": "AddAmount1"
    },
    "AddAmount1": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount.$": "States.MathAdd($.totalAmount, $.amounts[0])"
      },
      "Next": "CheckAmount2"
    },
    "CheckAmount2": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.amounts[1]",
          "IsPresent": true,
          "Next": "AddAmount2"
        }
      ],
      "Default": "TransformAggregates"
    },
    "AddAmount2": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount.$": "States.MathAdd($.totalAmount, $.amounts[1])"
      },
      "Next": "CheckAmount3"
    },
    "CheckAmount3": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.amounts[2]",
          "IsPresent": true,
          "Next": "AddAmount3"
        }
      ],
      "Default": "TransformAggregates"
    },
    "AddAmount3": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrders.$": "$.shippedOrders",
        "amounts.$": "$.amounts",
        "totalAmount.$": "States.MathAdd($.totalAmount, $.amounts[2])"
      },
      "Next": "TransformAggregates"
    },
    "TransformAggregates": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrderIds.$": "$.shippedOrders[*].orderId",
        "shippedOrderCount.$": "States.ArrayLength($.shippedOrders)",
        "totalShippedAmount.$": "$.totalAmount"
      },
      "Next": "CheckHighValue"
    },
    "CheckHighValue": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.totalShippedAmount",
          "NumericGreaterThanEqualsPath": "$.threshold",
          "Next": "SetHighValueTrue"
        }
      ],
      "Default": "SetHighValueFalse"
    },
    "SetHighValueTrue": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrderIds.$": "$.shippedOrderIds",
        "shippedOrderCount.$": "$.shippedOrderCount",
        "totalShippedAmount.$": "$.totalShippedAmount",
        "isHighValueCustomer": true,
        "comparisonText": "above"
      },
      "Next": "BuildReport"
    },
    "SetHighValueFalse": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "threshold.$": "$.threshold",
        "shippedOrderIds.$": "$.shippedOrderIds",
        "shippedOrderCount.$": "$.shippedOrderCount",
        "totalShippedAmount.$": "$.totalShippedAmount",
        "isHighValueCustomer": false,
        "comparisonText": "below"
      },
      "Next": "BuildReport"
    },
    "BuildReport": {
      "Type": "Pass",
      "Parameters": {
        "customerId.$": "$.customerId",
        "customerName.$": "$.customerName",
        "shippedOrderCount.$": "$.shippedOrderCount",
        "shippedOrderIds.$": "$.shippedOrderIds",
        "totalShippedAmount.$": "$.totalShippedAmount",
        "isHighValueCustomer.$": "$.isHighValueCustomer",
        "report.$": "States.Format('Customer {} (ID: {}) has {} shipped orders with total amount {}, which is {} threshold {}.', $.customerName, $.customerId, $.shippedOrderCount, $.totalShippedAmount, $.comparisonText, $.threshold)"
      },
      "End": true
    }
  }
}

今回もコードが少し長くなるので、「▶︎コード」にまとめました。クリックしてご確認ください。

  • JSONata
{
  "Comment": "実践ワークフロー – JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "BuildReport",
  "States": {
    "BuildReport": {
      "Type": "Pass",
      "Output": {
        "customerId": "{% $states.input.customer.id %}",
        "customerName": "{% $states.input.customer.name %}",
        "shippedOrderIds": "{% $states.input.customer.orders[status='SHIPPED'].orderId %}",
        "shippedOrderCount": "{% $count($states.input.customer.orders[status='SHIPPED']) %}",
        "totalShippedAmount": "{% $sum($states.input.customer.orders[status='SHIPPED'].amount) %}",
        "threshold": "{% $states.input.threshold %}",
        "isHighValueCustomer": "{% $sum($states.input.customer.orders[status='SHIPPED'].amount) >= $states.input.threshold %}",
        "report": "{% 'Customer ' & $states.input.customer.name & ' (ID: ' & $states.input.customer.id & ') has ' & $count($states.input.customer.orders[status='SHIPPED']) & ' shipped orders with total amount ' & $sum($states.input.customer.orders[status='SHIPPED'].amount) & ', which is ' & ($sum($states.input.customer.orders[status='SHIPPED'].amount) >= $states.input.threshold ? 'above' : 'below') & ' threshold ' & $states.input.threshold & '.' %}"
      },
      "End": true
    }
  }
}

JSONPathだと複雑なステップが、JSONataで1ステップで完結してしまいました…これは便利になったと噂になるはずですね…

まとめ

いかがでしたでしょうか?

今回はJSONPathでの複雑なフローが、JSONataで簡単に作成できることを体験してもらえるようなハンズオンを作成しました。

JSONPath単体で書いたところも、Lambdaなどを用いて、処理をもっと簡単に作成が可能ですが、Lambdaを組み合わせることにより、Lambda自体の保守対応(ランタイムのバージョンアップなど)が発生するので、JSONataでほぼノーコードで構築できるのはいいですね!

このブログが、みなさまのJSONataの理解につながれば幸いです。

AUTHOR
kim
kim
記事URLをコピーしました