← ブログに戻る

DynamoDB JSON + Step Functions — JSONataでLambdaなしデータ変換

シリーズ最終回は、ここまで学んだJSONataの知識を AWS Step Functions + DynamoDB で実践します。

第1回: JSONata入門 / 第2回: JSONata実践

ゴール

DynamoDB Scanで取得したDynamoDB JSONデータを、JSONataでフィルタ・変換し、通常のJSONで返す

これまではLambda関数を挟んでいた変換処理を、Step Functionsの設定だけで完結させます。

DynamoDB JSONとは

DynamoDBのAPIレスポンスは、通常のJSONとは異なる「型付きJSON」形式です。

// 通常のJSON
{
  "UserId": "user-001",
  "Name": "Alice",
  "Age": 30,
  "Active": true
}
// DynamoDB JSON
{
  "UserId": {"S": "user-001"},
  "Name": {"S": "Alice"},
  "Age": {"N": "30"},
  "Active": {"BOOL": true}
}

主な型記述子

記述子
SString{"S": "hello"}
NNumber(文字列で格納){"N": "42"}
BOOLBoolean{"BOOL": true}
LList{"L": [{"S": "a"}, {"S": "b"}]}
MMap{"M": {"key": {"S": "value"}}}
NULLNull{"NULL": true}

注意: 数値(N)は精度維持のため 文字列として 格納されます。JSONataで数値比較するには $number() での変換が必要です。

DynamoDB Scan結果の構造

DynamoDB:Scan アクションの結果は以下のような形になります:

{
  "Count": 3,
  "Items": [
    {
      "UserId": {"S": "user-001"},
      "Name": {"S": "Alice"},
      "Age": {"N": "30"}
    },
    {
      "UserId": {"S": "user-002"},
      "Name": {"S": "Bob"},
      "Age": {"N": "25"}
    },
    {
      "UserId": {"S": "user-003"},
      "Name": {"S": "Carol"},
      "Age": {"N": "28"}
    }
  ],
  "ScannedCount": 3
}

JSONataでの変換

基本: 通常JSONへの変換

Items.{
  "id": UserId.S,
  "name": Name.S,
  "age": $number(Age.N)
}

結果:

[
  {"id": "user-001", "name": "Alice", "age": 30},
  {"id": "user-002", "name": "Bob", "age": 25},
  {"id": "user-003", "name": "Carol", "age": 28}
]

ポイント:

  • .S で文字列値を取り出す
  • $number(.N) で数値に変換する
  • マッピング構文 array.{新構造} で一括変換

フィルタ付き: 条件で絞り込んで変換

Age > 25 のユーザーだけを取得する場合:

Items[$number(Age.N) > 25].{
  "id": UserId.S,
  "name": Name.S,
  "age": $number(Age.N)
}

結果:

[
  {"id": "user-001", "name": "Alice", "age": 30},
  {"id": "user-003", "name": "Carol", "age": 28}
]

$number(Age.N) でフィルタ内でも型変換しているのがポイントです。

Step FunctionsでのJSONata設定

QueryLanguageの設定

Step FunctionsでJSONataを使うには、ステートマシン定義で QueryLanguage を指定します:

{
  "Comment": "DynamoDB Scan with JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "ScanUsers",
  "States": {
    "ScanUsers": {
      "Type": "Task",
      "Resource": "arn:aws:states:::dynamodb:scan",
      "Arguments": {
        "TableName": "Users"
      },
      "Output": "{% $states.result.Items[$number(Age.N) > 25].{\"id\": UserId.S, \"name\": Name.S, \"age\": $number(Age.N)} %}",
      "End": true
    }
  }
}

{% %} デリミタ

JSONata式はASL内で {% %} で囲みます。この中にJSONata式を記述します。

主要フィールド

JSONataモードでは、従来の5つのJSONPathフィールドが2つに置き換わります:

従来(JSONPath)JSONataモード用途
InputPath + ParametersArgumentsアクションに送るデータ
ResultPath + ResultSelector + OutputPathOutput結果の変換

予約変数

変数内容
$states.inputステートへの入力データ
$states.resultタスク実行の結果
$states.errorOutputエラー情報
$states.contextコンテキスト情報(実行ARN等)

Assign — 変数の保存

Assign フィールドでワークフロー変数に値を保存し、後続ステートで再利用できます:

{
  "ScanUsers": {
    "Type": "Task",
    "Resource": "arn:aws:states:::dynamodb:scan",
    "Arguments": {
      "TableName": "Users"
    },
    "Assign": {
      "userCount": "{% $count($states.result.Items) %}",
      "filteredUsers": "{% $states.result.Items[$number(Age.N) > 25].{\"id\": UserId.S, \"name\": Name.S} %}"
    },
    "Next": "ProcessResults"
  }
}

後続ステートでは $userCount$filteredUsers として参照可能です。

実践例: 完全なステートマシン

DynamoDBからユーザーをScan → フィルタ → 整形して返す完全な例:

{
  "Comment": "DynamoDB を JSONata でフィルタ・変換",
  "QueryLanguage": "JSONata",
  "StartAt": "ScanTable",
  "States": {
    "ScanTable": {
      "Type": "Task",
      "Resource": "arn:aws:states:::dynamodb:scan",
      "Arguments": {
        "TableName": "{% $states.input.tableName %}"
      },
      "Assign": {
        "rawItems": "{% $states.result.Items %}"
      },
      "Next": "TransformData"
    },
    "TransformData": {
      "Type": "Pass",
      "Output": {
        "users": "{% $rawItems[$number(Age.N) > 25].{\"id\": UserId.S, \"name\": Name.S, \"age\": $number(Age.N)} %}",
        "totalCount": "{% $count($rawItems) %}",
        "filteredCount": "{% $count($rawItems[$number(Age.N) > 25]) %}"
      },
      "End": true
    }
  }
}

入力: {"tableName": "Users"}

出力:

{
  "users": [
    {"id": "user-001", "name": "Alice", "age": 30},
    {"id": "user-003", "name": "Carol", "age": 28}
  ],
  "totalCount": 3,
  "filteredCount": 2
}

Lambda関数はゼロ。ステートマシンの定義だけで、データの取得・フィルタ・変換がすべて完結しています。

まとめ

ステップやること
1. QueryLanguage設定"QueryLanguage": "JSONata"
2. DynamoDB ScanArguments でテーブル名を指定
3. 型変換.S で文字列、$number(.N) で数値を取り出す
4. フィルタItems[条件] で絞り込み
5. 変換array.{新しい構造} で通常JSONに整形
6. 出力/変数保存Output で最終結果、Assign で中間変数を保存

これでJSONataの基礎から実践まで一通り学べました。ぜひ JSONata Learning アプリ で復習してみてください!

関連リンク