NO IMAGE

Step Functions 5大入出力フィルタ完全ガイド InputPath ResultPath 実践

NO IMAGE
目次

1. この記事について

fig01: SF 実践編シリーズ全体マップ (1400x800)

本記事の想定読者チェックリスト

  • AWS Step Functions 入門記事を読み終え、State / Execution / Task の基本を理解している
  • Terraform で AWS リソース (aws_sfn_state_machine 等) を定義した経験がある
  • aws stepfunctions get-execution-history の出力が何を意味するか気になっている
  • InputPath / Parameters / ResultSelector / ResultPath / OutputPath の使い分けで迷ったことがある
5 フィルタの適用順序を間違えるとよくある事故 3 つ

  • 事故1 — ResultPath 指定漏れ: Task 結果で元入力が丸ごと上書きされ、後続 State で $.userId が参照できなくなる。デフォルトは "ResultPath": "$" (上書き) であることを知らないと発生する。
  • 事故2 — Parameters と ResultSelector の責務混同: Parameters は Task への「入力」を作り直すフィルタ、ResultSelector は Task の「出力」を整形するフィルタ。逆に設定すると効果がゼロになる。
  • 事故3 — null InputPath の誤解: "InputPath": null は空 {} を Task に渡す。"InputPath": "$" (全入力を渡す) とは全く異なる。デバッグ時の混乱の原因になりやすい。

1-1. SF 実践編 Vol1 とは

AWS Step Functions の入門記事(SF 入門)で State / Execution / Task の基本を習得した読者が次に直面する壁が「実戦でワークフローが壊れる」問題です。入力 JSON がどこかで消える、Task 結果が後続 State に届かない、Map の中だけ Context Object の参照が変わる——こうした問題のほとんどは 5 大入出力フィルタの理解不足が原因です。

Step Functions のデバッグで最も時間がかかるのが「どのフィルタが JSON を変換したのか」の特定です。get-execution-history の出力を正しく読み解けると、デバッグ時間を大幅に短縮できます。

SF シリーズはこれまで、入門 でワークフロー作成の基礎を、ECS 統合 で実践的なバッチ処理を、エラーハンドリング で障害対応を、旧データフロー でフィルタの概念を解説してきました。本記事はその集大成として「実践編 Vol1」を位置付け、フィルタの挙動を get-execution-history の実出力 で一行ずつ追跡できる形で提供します。

本記事は SF 実践編 Vol1 として、InputPath / Parameters / ResultSelector / ResultPath / OutputPath の 5 フィルタを Terraform HCL + ASL + CLI 実挙動ダンプの 3 点セット で解説します。各フィルタが「State 実行のどの段階で、どの JSON を変換するか」を get-execution-history の実出力で一行ずつ追跡できるようにすることをゴールとします。

旧版 との最大の違いは「実挙動の可視化」です。旧版が概念の説明に重点を置いていたのに対し、本記事は実際に start-execution コマンドを実行し、その結果を get-execution-history で確認することで、フィルタの動作を具体的に学べるように構成しています。

なお、本記事のタイトルに「実践編 Vol1」と銘打っている理由は、SF シリーズの続編として以下の実践編を計画しているためです。

  • 実践編 Vol2: Callback パターン (.waitForTaskToken) 実戦 (既存 ID:1087 の発展版)
  • 実践編 Vol3: Distributed Map (S3 Manifest 処理) 実戦
  • 実践編 Vol4: Express vs Standard の料金・用途比較

各実践編は独立して読めますが、本記事で 5 フィルタを習得しておくと後続の実践編の理解がスムーズになります。

1-2. 本記事のゴール

本記事を読み終えると、以下の 4 つを自力で行えるようになります。

  1. get-execution-history 読解: stateEnteredEventDetails.inputstateExitedEventDetails.output の差分から、どのフィルタが何を変換したかを即時特定できる。stateEnteredEventDetails.input が InputPath + Parameters 適用後、stateExitedEventDetails.output が ResultSelector + ResultPath + OutputPath 適用後であることを理解できる。
  2. フィルタ設計: 新規 State を作成する際に 5 フィルタの適用順序 (InputPath → Parameters → Task → ResultSelector → ResultPath → OutputPath) を意識しながら ASL を設計できる。元の入力 JSON を後続 State に引き継ぎたい場合に ResultPath を正しく設定できる。
  3. Map × フィルタ: Map State の ItemSelector (2023 以降の Parameters の Map 版の新名称) と ResultSelector の責務を切り分け、$$.Map.Item.Value 等の Context Object 参照を使って Map 内の入出力を意図通りに制御できる。
  4. チートシート活用: §8 のチートシート 1 枚を手元に置き、新規 State を作成する際のフィルタ設計を 5 分以内で完了できる。レビュー時に 5 フィルタの適用順序を即答できる。

1-3. 前提知識

本記事は以下の知識を前提とします。

  • Step Functions 基本: Task / Choice / Parallel / Map / Pass / Wait / Succeed / Fail の 8 State 種別を入門記事(SF 入門)で理解済みであること。State Machine の定義方法、Execution の起動・確認方法を把握していること。
  • Terraform: aws_sfn_state_machine リソースを Terraform 1.x / hashicorp/aws ~> 5.70 で定義した経験があること。jsonencode と HEREDOC による ASL の埋め込み方法を知っていること。
  • AWS CLI: aws-cli v2 がインストール済みで、aws stepfunctions start-execution および aws stepfunctions get-execution-history コマンドが実行できること。プロファイルや IAM 権限の設定が完了していること。
  • JSON / JSONPath 基礎: JSON のオブジェクト・配列の概念を理解していること。$.foo.bar のような JSONPath 記法を見たことがあること (完全に理解していなくても §2-4 で補足します)。

これらの前提が不足している場合は、先に入門記事を確認してから本記事に戻ることをお勧めします。特に Step Functions の基本概念は SF 入門記事 で体系的に学べます。

1-4. 既存 SF 記事との位置付け

本記事は SF シリーズ第 6 弾として、既存 5 記事の「基礎」から「実践」へのステップアップを担います。

位置付けWP IDタイトル内容
基礎・入門1033AWS Step Functions 入門State / Execution / Task の基本概念
基礎・実践1045ECS × Step Functions CSV バッチECS Task との統合パターン
基礎・エラー1057Retry / Catch / Timeout 完全ガイドエラーハンドリングの全パターン
基礎・データフロー (旧版)1080データフロー制御完全ガイド 旧版5 フィルタの概念・基本構文中心
基礎・Callback1087Callback パターン完全ガイド.waitForTaskToken の仕組みと実装
実践編 Vol1 (本記事)新規5 大入出力フィルタ完全ガイド3 点セット + Map/Parallel 組合せ

旧版 との差別化:

比較軸旧版本記事 (実践編 Vol1)
解説スタイル概念・基本構文中心CLI 実挙動ダンプで追跡
フィルタ組合せ単一 State での説明Map / Parallel / Choice との実戦組合せ (§7)
参照資料概念図チートシート (§8) で暗記型リファレンス化
Intrinsic Functions基礎的な紹介実例コード + 追加履歴 (2022+ 新 Functions 含む)

旧版で 5 フィルタの概念を把握済みであれば、本記事を読むことで「実戦で使える」レベルに引き上げられます。概念から学び直したい場合は旧版 (ID:1080) を先に参照してください。

1-5. 本記事で扱う範囲と扱わない範囲

扱う内容:
– InputPath / Parameters / ResultSelector / ResultPath / OutputPath の 5 フィルタ全構文と実挙動
– Map State の ItemSelector (2023 以降の新名称)、Parallel Branches への入力伝搬、Choice State の Variable 指定
– Terraform HCL (aws_sfn_state_machine) + ASL + CLI の 3 点セット (各章で必須)
– Intrinsic Functions (States.Format / States.Array / States.StringToJson / States.UUID 等)
– JSONPath 記法サマリ (SF サポートサブセット + 未サポート記法の明示)

扱わない内容:
– SF の基本概念 → SF 入門
– Retry / Catch / Timeout → エラーハンドリング記事
– Callback (.waitForTaskToken) → Callback 記事 へ (本記事の §8-5 次回予告でも紹介)
– Distributed Map (S3 Manifest / inventory) → 将来の実践編 Vol3 相当
– Express vs Standard の料金・用途比較 → 将来の実践編 Vol4 相当
– SDK Direct Integration (optimized 統合) → 将来の実践編 Vol5 相当

1-6. 2026 年 4 月時点の API バージョン宣言

本記事のコードサンプルは 2026 年 4 月時点 の以下のバージョンで動作確認しています。AWS のサービスは頻繁に更新されるため、本記事の確認時点を明示します。

ASL (Amazon States Language):
States.Language 最新仕様に準拠。"Comment" フィールドは任意です。
– State の種別は Task / Choice / Parallel / Map / Pass / Wait / Succeed / Fail の 8 種類 (2026-04 確認済み)。
– Map State の "ItemSelector" は 2023 年以降の新名称で、旧名称 "Parameters" は非推奨ですが後方互換性は維持されています。

Intrinsic Functions 追加履歴:
– 2019 年以前: JSONPath のみ (Intrinsic Functions なし)
– 2021 年追加: States.Format / States.Array / States.StringToJson / States.JsonToString
– 2022 年追加: States.UUID / States.Hash / States.Base64Encode / States.Base64Decode / States.ArrayContains / States.ArrayGetItem / States.ArrayLength / States.ArrayMerge / States.ArrayPartition / States.ArrayRange / States.ArrayUnique / States.MathAdd / States.MathRandom / States.StringSplit / States.JsonMerge
– 2026-04 時点で上記全 Functions が利用可能。本記事では主要なものを §2-5 で実例付きで解説します。

Terraform:
hashicorp/aws ~> 5.70 / Terraform 1.x を使用。
aws_sfn_state_machine リソースの definition フィールドで ASL を HEREDOC または jsonencode で定義します。
aws_iam_role + aws_iam_role_policy による IAM 設定を §2-6 で定義し、以降の実例で再利用します。

AWS CLI:
aws-cli v2 を使用 (aws stepfunctions get-execution-history 等)。
– v1 とは --query オプションの挙動が一部異なるため、v2 での実行を推奨します。

SF 入門記事で基礎を復習する — State/Execution/Task の基本

2. 5 フィルタ全体像

fig02: 5 大フィルタ処理フロー (1400x800)

2-1. State 実行パイプラインの全体像

Step Functions の 1 つの State が実行される際、入力 JSON は最大 5 段のフィルタを経由してから次の State に渡されます。この処理順序を正確に把握することが、すべての入出力トラブル解決の出発点になります。

raw input
  └─[InputPath]──────────────→ Task への流入 JSON を絞り込む
└─[Parameters]─────────→ Task が受け取る入力 JSON を作り直す
  │
  ▼
 Task 実行 (Lambda / ECS / DynamoDB 等)
  │
  ▼
 Task 生出力 JSON
└─[ResultSelector]─────→ Task 生出力から必要フィールドだけ抽出
  └─[ResultPath]─────→ 整形済み結果を元入力 JSON のどこに差し込むか決める
└─[OutputPath]─→ State の最終出力として次 State に渡す部分を絞り込む
↓
 next state raw input

各フィルタはすべて省略可能です。省略した場合のデフォルト動作は以下のとおりです。

フィルタ省略時のデフォルト値省略時の挙動
InputPath"$"State への流入 JSON をそのまま全て Task に渡す
Parameters(省略)InputPath 適用後の JSON をそのまま Task に渡す
ResultSelector(省略)Task 生出力をそのまま ResultPath に渡す
ResultPath"$"Task 結果で State 入力 JSON を丸ごと上書き
OutputPath"$"ResultPath 適用後の JSON を全て次 State に渡す

「5 フィルタを全部省略した場合」は「Task 生出力がそのまま次 State の入力になる」ことを意味します。シンプルな Pass State や Fire-and-forget Task ではこの挙動で十分ですが、元の入力 JSON を後続 State で参照したい場合は ResultPath の設定が必須になります。

get-execution-history で確認できる変換ポイントは以下の 3 イベントです。

  • TaskStateEntered.stateEnteredEventDetails.input → InputPath + Parameters 適用後の JSON (Task への実際の入力)
  • TaskSucceeded.taskSucceededEventDetails.output → Task 生出力 (ResultSelector 適用前)
  • TaskStateExited.stateExitedEventDetails.output → ResultSelector + ResultPath + OutputPath 適用後の JSON (次 State への実際の入力)

2-2. 各フィルタ 1 行サマリ

5 フィルタの役割・適用タイミング・操作対象・デフォルト値・典型用途を表でまとめます。

フィルタ適用タイミング操作対象デフォルト値典型用途
InputPathTask 実行前State への流入 JSON"$" (全体)巨大入力から必要なサブツリーだけを Task に渡す
ParametersTask 実行前 (InputPath 後)InputPath 適用後の JSON(省略=素通し)Task 入力を完全に作り直す。Context Object ($$) の参照もここ
ResultSelectorTask 実行後Task の生出力 JSON(省略=素通し)Lambda の巨大レスポンスから必要フィールドだけ抽出
ResultPathResultSelector 後元入力 JSON + Task 整形済み結果"$" (上書き)元入力 JSON を維持しながら Task 結果を特定フィールドへ差し込む
OutputPathResultPath 後ResultPath 適用後の JSON"$" (全体)次 State に渡す情報をさらに絞り込む

Parameters と ResultSelector は「作り直し系」、InputPath / OutputPath は「絞り込み系」、ResultPath は「マージ系」と覚えると混乱しにくくなります。

2-3. よくある誤解 TOP 3

よくある誤解 TOP 3 — フィルタ設計での典型的な失敗パターン

誤解 1: 「Parameters は State への入力 JSON をそのまま書き換える」

実際は違います。Parameters は「Task が受け取る新しい JSON を一から作り直す」フィルタです。元の入力 JSON に存在するフィールドでも、Parameters で明示的に記述しなければ Task には届きません。「元入力を加工する」ではなく「新しい JSON を構築する」と理解してください。

誤解 2: 「ResultSelector と ResultPath は同じことをしている」

全く異なります。ResultSelector は Task の生出力だけを対象に必要フィールドを抽出します。ResultPath は 元の State 入力 JSON に対して「ResultSelector で整形した結果をどこに差し込むか」を決めます。ResultSelector が「Task 結果の整形」、ResultPath が「元入力との統合」です。

誤解 3: 「OutputPath を省略すると全部の情報が次 State に渡る」

省略時のデフォルトは "OutputPath": "$" なので「全体を渡す」は正しいです。ただし「全体」とは ResultPath 適用後の JSON 全体を指します。ResultPath のデフォルトが "$" (元入力全体を Task 結果で上書き) であることと組み合わさると、意図せず元入力が消えることがあります。

エラーハンドリング (Retry / Catch) と ResultPath の相性については Retry/Catch/Timeout 完全ガイド も参照してください。Catch の ResultPath でエラー情報を元入力に追記するパターンは実戦で頻出します。

2-4. JSONPath 記法サマリ

Step Functions が採用する JSONPath はフル仕様のサブセットです。サポートされる記法とサポートされない記法を明確に把握しておくことで、実行時エラーを未然に防げます。

記法意味サポート
$ルートオブジェクト全体
$.fooトップレベルの foo フィールド
$.foo.barネストされた bar フィールド
$.items[0]配列の 0 番目の要素
$.items[*]配列の全要素
$.items[-1]配列の最後の要素
$..[?(@.status=="ok")]filter 式× 未サポート
$..foo再帰的な子孫検索× 未サポート

filter 式 (?()) は Step Functions では使えません。filter 式が必要な場合は Lambda で前処理するか、Choice State の IsPresent / StringEquals 等の比較条件で代替してください。

旧データフロー記事との関係

5 フィルタの JSONPath 記法の概念を基礎から復習したい場合は 旧データフロー完全ガイド も参照してください。本記事はその発展版として、get-execution-history 実挙動ダンプ・Map/Parallel 組合せ・Intrinsic Functions の実例に重点を置いています。旧版で概念を掴んでから本記事で実戦力を高めるという読み方が最もスムーズです。

2-5. Intrinsic Functions

Intrinsic Functions は ASL の Parameters / ResultSelector / ItemSelector の中で使える組み込み関数群です。JSONPath だけでは表現できない変換をサーバーレスに実現できます。2026-04 時点で利用可能な主要 Functions は以下のとおりです。

文字列操作:

{
  "message.$": "States.Format('Hello, {}! Your order {} is ready.', $.name, $.orderId)"
}

States.Format は Python の str.format() に相当します。{} プレースホルダーが JSONPath の評価結果で置換されます。

配列構築:

{
  "items.$": "States.Array($.item1, $.item2, $.item3)"
}

複数の JSONPath 参照から配列を動的に構築します。Map State の前処理で特に有用です。

JSON 文字列変換:

{
  "parsedPayload.$": "States.StringToJson($.rawPayload)",
  "serialized.$": "States.JsonToString($.objectField)"
}

Lambda が JSON を文字列として返す場合 (States.StringToJson) や、DynamoDB に JSON を文字列として保存する場合 (States.JsonToString) に使います。

ユニーク ID・ハッシュ (2022 年以降追加、2026-04 確認済み):

{
  "requestId.$": "States.UUID()",
  "checksum.$": "States.Hash($.payload, 'SHA-1')"
}

States.UUID() は RFC 4122 準拠の UUID v4 を生成します。実行ごとにユニークな ID が必要なワークフロー (冪等性キー等) で活用します。States.Hash はペイロードの整合性検証に使います。

2-6. 本章で使う実験用ステートマシン

§3 以降の実例では以下の最小構成ステートマシンをベースとして使います。Pass State 1 つで構成し、フィルタを差し替えながら各フィルタの挙動を実験します。

# iam.tf — Step Functions 実行ロール (§3 以降で共通使用)
resource "aws_iam_role" "sfn_exec" {
  name = "sfn-io-filters-lab-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "states.amazonaws.com" }
Action = "sts:AssumeRole"
 }]
  })
}

resource "aws_iam_role_policy" "sfn_exec_inline" {
  name = "sfn-io-filters-lab-policy"
  role = aws_iam_role.sfn_exec.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect= "Allow"
Action= ["lambda:InvokeFunction", "logs:CreateLogGroup", "logs:CreateLogDelivery", "logs:PutLogEvents"]
Resource = "*"
 }]
  })
}
# main.tf — 実験用ステートマシン (Pass State 最小構成)
resource "aws_sfn_state_machine" "io_filters_lab" {
  name  = "io-filters-lab"
  role_arn = aws_iam_role.sfn_exec.arn

  definition = jsonencode({
 Comment = "5 filters experiment base"
 StartAt = "PassThrough"
 States = {
PassThrough = {
  Type = "Pass"
  End  = true
}
 }
  })
}
# 実行開始
aws stepfunctions start-execution \
  --state-machine-arn "arn:aws:states:ap-northeast-1:123456789012:stateMachine:io-filters-lab" \
  --name "test-run-001" \
  --input '{"name":"taro","items":[1,2,3],"order":{"id":"ORD-001","total":5000}}'
# 実行履歴確認
aws stepfunctions get-execution-history \
  --execution-arn "arn:aws:states:ap-northeast-1:123456789012:execution:io-filters-lab:test-run-001" \
  --output json
5 フィルタ適用順序の暗記法

  • 頭文字: IP → P → RS → RP → OP (Input Path → Parameters → Result Selector → Result Path → Output Path)
  • 前半 (IP/P) = 入力側: Task に渡す前に JSON を整える
  • 後半 (RS/RP/OP) = 出力側: Task から受け取った後に JSON を整える
  • null の意味の違い: InputPath null = 空 {} を渡す / ResultPath null = Task 結果を破棄 / OutputPath null = 空 {} を次 State に渡す

3. InputPath 詳解

fig03: InputPath + Parameters 変換図 (1400x800)

3-1. InputPath の役割

InputPath は、State が受け取る JSON の中から「Task に渡したい部分だけ」を切り出すフィルタだ。State Machine に流れ込む実行入力 (Execution Input) がいくら巨大でも、その State が必要な部分のみを取り出して Task に渡せる。

デフォルト値は $(全入力)"InputPath": "$" と明示しても省略しても動作は変わらない。InputPath を省略した State には実行入力がそのまま流れ込む。

InputPath が適用されるタイミングは 5 フィルタの中で最初だ。

Execution Input
 └─ InputPath ─→ Parameters ─→ [Task 実行] ─→ ResultSelector ─→ ResultPath ─→ OutputPath

InputPath の値は JSONPath 式で指定する。Step Functions がサポートするサブセットで書く必要があり、filter 式 $.items[?(@.status=="active")] のような複雑な述語は未サポートだ。指定できるのはノード参照と配列インデックス参照のみ。

get-execution-history で確認する際、stateEnteredEventDetails.input フィールドが InputPath 適用の JSON を示す。つまり State に実際に流れ込んだ値がここに記録される。

3-2. 実例 1: ネスト抽出

入力 JSON の深いパスに格納されたデータだけを Task に渡す例として、$.order.items を抽出する実装を示す。

Terraform HCL

# IAM ロール (Step Functions 用)
resource "aws_iam_role" "sfn_role" {
  name = "sfn-inputpath-demo-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "states.amazonaws.com" }
Action = "sts:AssumeRole"
 }]
  })
}

resource "aws_iam_role_policy" "sfn_policy" {
  name = "sfn-inputpath-demo-policy"
  role = aws_iam_role.sfn_role.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect= "Allow"
Action= ["logs:CreateLogDelivery", "logs:GetLogDelivery"]
Resource = "*"
 }]
  })
}

# ステートマシン — InputPath でネスト抽出
resource "aws_sfn_state_machine" "inputpath_nested" {
  name  = "inputpath-nested-demo"
  role_arn = aws_iam_role.sfn_role.arn

  definition = <<EOT
{
  "Comment": "InputPath ネスト抽出デモ",
  "StartAt": "ExtractItems",
  "States": {
 "ExtractItems": {
"Type": "Pass",
"InputPath": "$.order.items",
"ResultPath": "$",
"End": true
 }
  }
}
EOT
}

ASL 抜粋

{
  "Comment": "InputPath ネスト抽出デモ",
  "StartAt": "ExtractItems",
  "States": {
 "ExtractItems": {
"Type": "Pass",
"InputPath": "$.order.items",
"ResultPath": "$",
"End": true
 }
  }
}

CLI 実行ダンプ

# 実行開始
aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:inputpath-nested-demo \
  --input '{"order":{"items":[1,2,3],"customer":{"id":"u1","name":"taro"}}}'
{
 "executionArn": "arn:aws:states:ap-northeast-1:123456789012:execution:inputpath-nested-demo:exec-001",
 "startDate": "2026-04-23T09:00:00.000Z"
}
# 実行履歴取得 (抜粋)
aws stepfunctions get-execution-history \
  --execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:inputpath-nested-demo:exec-001
[
  {
 "timestamp": "2026-04-23T09:00:00.001Z",
 "type": "ExecutionStarted",
 "executionStartedEventDetails": {
"input": "{\"order\":{\"items\":[1,2,3],\"customer\":{\"id\":\"u1\",\"name\":\"taro\"}}}"
 }
  },
  {
 "timestamp": "2026-04-23T09:00:00.010Z",
 "type": "PassStateEntered",
 "stateEnteredEventDetails": {
"name": "ExtractItems",
"input": "[1,2,3]"
 }
  },
  {
 "timestamp": "2026-04-23T09:00:00.020Z",
 "type": "ExecutionSucceeded",
 "executionSucceededEventDetails": {
"output": "[1,2,3]"
 }
  }
]

stateEnteredEventDetails.input[1,2,3] になっている点に注目。元の "customer" オブジェクトは InputPath によって取り除かれ、State には $.order.items の値だけが流れ込んでいる。

3-3. 実例 2: null InputPath

"InputPath": null を指定すると、State へ流れ込む入力が 空オブジェクト {} になる。

{
  "Comment": "InputPath null デモ",
  "StartAt": "NoInputTask",
  "States": {
 "NoInputTask": {
"Type": "Pass",
"InputPath": null,
"Result": {"status": "triggered"},
"End": true
 }
  }
}

典型用途: 外部サービスの Webhook をトリガーするだけで、入力内容を Task 側で必要としない場合。Lambda が Context Object の $$.Execution.Id だけで動作できる場合も同様だ。

CLI 確認

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:inputpath-null-demo \
  --input '{"order":{"items":[1,2,3],"customer":{"id":"u1"}}}'
{
  "type": "PassStateEntered",
  "stateEnteredEventDetails": {
 "name": "NoInputTask",
 "input": "{}"
  }
}

元の入力がどれだけ大きくても、"InputPath": null を指定した State に到達した瞬間に {} に置き換わる。

"InputPath": null"InputPath": "$" の違い

設定State への入力
"InputPath": "$" または省略実行入力 (または前 State の出力) をそのまま渡す
"InputPath": null空オブジェクト {} を渡す。元入力は完全に捨てられる

この違いを混同すると、Task が $.userId を参照しているのに入力が {} になって KeyNotFound エラーが出る事故が起きる。

3-4. 実例 3: 配列要素抽出

JSONPath の配列インデックス参照で配列の特定要素だけを取り出せる。

{
  "States": {
 "FirstItem": {
"Type": "Pass",
"InputPath": "$.items[0]",
"ResultPath": "$",
"Next": "AllItems"
 },
 "AllItems": {
"Type": "Pass",
"InputPath": "$.items",
"ResultPath": "$",
"End": true
 }
  }
}

CLI 差分確認

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:inputpath-array-demo \
  --input '{"items":[10,20,30]}'
# "FirstItem" State への入力 (InputPath="$.items[0]")
{
  "type": "PassStateEntered",
  "stateEnteredEventDetails": {
 "name": "FirstItem",
 "input": "10"
  }
}

# "AllItems" State への入力 (InputPath="$.items")
{
  "type": "PassStateEntered",
  "stateEnteredEventDetails": {
 "name": "AllItems",
 "input": "[10,20,30]"
  }
}

$.items[0] はスカラー値 10$.items は配列全体 [10,20,30] を渡す。Map State でイテレーションしたい場合は $.items(配列全体)を渡して ItemsPath で受け取るのが正解だ。$.items[0] で単一要素を渡すと Map State が Items must be an array エラーになる。

3-5. 陥りやすいミス

InputPath でよくある 3 つの落とし穴

  • 落とし穴 1: filter 式の使用$.items[?(@.status=="active")] のような述語 filter は Step Functions の JSONPath 実装では未サポート。実行時に InvalidParameterValue: The value for the field 'InputPath' is not valid が返る。フィルタリングが必要な場合は Lambda や Pass State の Parameters と Intrinsic Functions で代替する。
  • 落とし穴 2: null と “$” の混同"InputPath": null は空 {} を渡す。"InputPath": "$" は全入力をそのまま渡す。どちらも「省略と同じ」と思い込んでいると事故になる。
  • 落とし穴 3: 存在しないパスの指定 — 実行時に InputPath が指す JSON パスが存在しない場合、Step Functions は States.Runtime エラーを返す。入力スキーマが可変な場合は Choice State で事前チェックするか、Parameters と States.JsonMerge でデフォルト値を補完する。

JSONPath 構文エラーは Workflow Studio のビジュアルエディタでは検出できないことが多い。ローカルで aws stepfunctions test-state(2023 以降で利用可)を使ってユニット的にテストする運用が安全だ。

3-6. Terraform 実装パターン

locals ブロックで ASL を jsonencode を使って動的生成すると、変数をそのまま埋め込めて可読性が上がる。

locals {
  input_path = "$.order.items"

  state_machine_definition = jsonencode({
 Comment = "InputPath 動的生成デモ"
 StartAt = "ExtractItems"
 States = {
ExtractItems = {
  Type= "Pass"
  InputPath = local.input_path
  End = true
}
 }
  })
}

resource "aws_sfn_state_machine" "dynamic_inputpath" {
  name = "dynamic-inputpath-demo"
  role_arn= aws_iam_role.sfn_role.arn
  definition = local.state_machine_definition
}

for_each での複数ステートマシン生成(フィルタ切り替え実験用)

locals {
  filter_states = {
 "extract-items" = "$.order.items"
 "extract-customer" = "$.order.customer"
  }
}

resource "aws_sfn_state_machine" "multi_inputpath" {
  for_each = local.filter_states

  name  = "inputpath-${each.key}-demo"
  role_arn = aws_iam_role.sfn_role.arn

  definition = jsonencode({
 Comment = "InputPath=${each.value} デモ"
 StartAt = "FilterState"
 States = {
FilterState = {
  Type= "Pass"
  InputPath = each.value
  End = true
}
 }
  })
}

jsonencode を使うと HCL の変数・ローカルを直接 ASL に埋め込めるため、HEREDOC で文字列結合するより保守性が高い。terraform validate で HCL 構文チェックが効き、不正な JSON 生成を早期に検出できる点も利点だ。

4. Parameters & ResultSelector 詳解

4-1. Parameters の役割

Parameters は、Task が受け取る入力 JSON を新しく作り直すフィルタだ。InputPath で絞り込まれた入力を素材にして、Task 専用のペイロードを構築するイメージで使う。

.$ suffix による JSONPath 参照: Parameters のキーが .$ で終わる場合、その値は JSONPath 式として評価される。

"Parameters": {
  "staticKey": "固定値",
  "dynamicKey.$": "$.someField",
  "contextKey.$": "$$.Execution.Id"
}
  • "staticKey": JSONPath ではなくリテラル値として扱われる
  • "dynamicKey.$": 入力 JSON の $.someField の値が展開される
  • "contextKey.$": Context Object($$)から実行 ID が展開される

デフォルト値は存在しない。Parameters を省略した State では InputPath の出力がそのまま Task に渡る。Parameters を指定すると、InputPath の結果は完全に破棄されて Parameters で定義した新しい JSON が Task に渡る点が重要だ。

4-2. 実例 1: Parameters の基本

Lambda Task に対して、入力から必要なフィールドだけを選んで渡す実装例を示す。

Terraform HCL

# Lambda 実行権限を付与
resource "aws_iam_role_policy" "sfn_lambda_policy" {
  name = "sfn-parameters-lambda-policy"
  role = aws_iam_role.sfn_role.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect= "Allow"
Action= ["lambda:InvokeFunction"]
Resource = aws_lambda_function.processor.arn
 }]
  })
}

resource "aws_sfn_state_machine" "parameters_basic" {
  name  = "parameters-basic-demo"
  role_arn = aws_iam_role.sfn_role.arn

  definition = jsonencode({
 Comment = "Parameters 基本デモ"
 StartAt = "ProcessOrder"
 States = {
ProcessOrder = {
  Type  = "Task"
  Resource = aws_lambda_function.processor.arn
  Parameters = {
 "userName.$"  = "$.name"
 "orderId.$"= "$.order.id"
 "timestamp.$" = "$$.State.EnteredTime"
  }
  ResultPath = "$.result"
  End  = true
}
 }
  })
}

ASL 抜粋

{
  "ProcessOrder": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:processor",
 "Parameters": {
"userName.$": "$.name",
"orderId.$": "$.order.id",
"timestamp.$": "$$.State.EnteredTime"
 },
 "ResultPath": "$.result",
 "End": true
  }
}

CLI 実行ダンプ

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:parameters-basic-demo \
  --input '{"name":"taro","order":{"id":"ORD-001","items":[1,2,3]},"region":"ap-northeast-1"}'
[
  {
 "type": "ExecutionStarted",
 "executionStartedEventDetails": {
"input": "{\"name\":\"taro\",\"order\":{\"id\":\"ORD-001\",\"items\":[1,2,3]},\"region\":\"ap-northeast-1\"}"
 }
  },
  {
 "type": "TaskStateEntered",
 "stateEnteredEventDetails": {
"name": "ProcessOrder",
"input": "{\"name\":\"taro\",\"order\":{\"id\":\"ORD-001\",\"items\":[1,2,3]},\"region\":\"ap-northeast-1\"}"
 }
  },
  {
 "type": "TaskScheduled",
 "taskScheduledEventDetails": {
"resourceType": "lambda",
"resource": "invoke",
"parameters": "{\"FunctionName\":\"arn:aws:lambda:ap-northeast-1:123456789012:function:processor\",\"Payload\":{\"userName\":\"taro\",\"orderId\":\"ORD-001\",\"timestamp\":\"2026-04-23T09:00:00.010Z\"}}"
 }
  },
  {
 "type": "TaskSucceeded",
 "taskSucceededEventDetails": {
"resourceType": "lambda",
"resource": "invoke",
"output": "{\"StatusCode\":200,\"Payload\":{\"status\":\"ok\"}}"
 }
  }
]

TaskScheduled イベントの parameters フィールドを確認すると、Lambda が実際に受け取るペイロードが {"userName":"taro","orderId":"ORD-001","timestamp":"2026-04-23T09:00:00.010Z"} になっている。元の "items""region" フィールドは Parameters で定義されていないため Lambda には渡っていない。$$.State.EnteredTime は Context Object から展開されたタイムスタンプだ。

4-3. 実例 2: Intrinsic Functions の活用

Intrinsic Functions を使うと、文字列結合や配列構築を ASL 内で完結させられる。.$ suffix と組み合わせて使う。

文字列フォーマット — States.Format

{
  "SendNotification": {
 "Type": "Task",
 "Resource": "arn:aws:states:::sns:publish",
 "Parameters": {
"TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:order-notifications",
"Message.$": "States.Format('注文 {} を受け付けました。担当: {}', $.order.id, $.name)",
"Subject": "注文受付完了"
 },
 "End": true
  }
}

配列構築 — States.Array

{
  "BuildArray": {
 "Type": "Pass",
 "Parameters": {
"combined.$": "States.Array($.item1, $.item2, $.item3)"
 },
 "End": true
  }
}

CLI 確認

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:intrinsic-demo \
  --input '{"name":"hanako","order":{"id":"ORD-002"},"item1":"A","item2":"B","item3":"C"}'
{
  "type": "TaskScheduled",
  "taskScheduledEventDetails": {
 "parameters": "{\"Message\":\"注文 ORD-002 を受け付けました。担当: hanako\",\"Subject\":\"注文受付完了\"}"
  }
}

States.Format{} プレースホルダは引数の順に置換される。2 つ目以降の引数は JSONPath 参照か固定値を指定できる。

4-4. ResultSelector の役割

ResultSelector は、Task が返した出力 JSON を State 内で保持する前に整形するフィルタだ。Parameters と役割が似ているが、適用タイミングと対象が異なる。

  • Parameters: Task 実行に Task への入力を作り直す
  • ResultSelector: Task 実行に Task の出力を整形する

Lambda の戻り値には StatusCodeExecutedVersionPayload 等の Lambda 固有フィールドが含まれる。Task の呼び出し元が必要なのはたいてい Payload だけだ。ResultSelector で不要フィールドを除去することで、後続 State への JSON を簡潔に保てる。

.$ suffix の使い方は Parameters と同じで、.$ が付いたキーは JSONPath として評価される。

デフォルト値は存在しない。ResultSelector を省略すると Task の生出力がそのまま次の処理(ResultPath)に渡る。

4-5. 実例 3: ResultSelector

Lambda から返る巨大レスポンスの中から必要なフィールドだけを取り出す例を示す。

Terraform HCL

resource "aws_sfn_state_machine" "result_selector_demo" {
  name  = "result-selector-demo"
  role_arn = aws_iam_role.sfn_role.arn

  definition = jsonencode({
 Comment = "ResultSelector デモ"
 StartAt = "InvokeProcessor"
 States = {
InvokeProcessor = {
  Type  = "Task"
  Resource = aws_lambda_function.processor.arn
  Parameters = {
 "userId.$" = "$.userId"
  }
  ResultSelector = {
 "statusCode.$" = "$.StatusCode"
 "body.$" = "$.Payload"
  }
  ResultPath = "$.lambdaResult"
  End  = true
}
 }
  })
}

ASL 抜粋

{
  "InvokeProcessor": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:processor",
 "Parameters": {
"userId.$": "$.userId"
 },
 "ResultSelector": {
"statusCode.$": "$.StatusCode",
"body.$": "$.Payload"
 },
 "ResultPath": "$.lambdaResult",
 "End": true
  }
}

CLI 実行ダンプ

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:result-selector-demo \
  --input '{"userId":"u1","sessionToken":"tok-abc"}'
[
  {
 "type": "TaskSucceeded",
 "taskSucceededEventDetails": {
"resourceType": "lambda",
"resource": "invoke",
"output": "{\"StatusCode\":200,\"ExecutedVersion\":\"$LATEST\",\"Payload\":{\"status\":\"ok\",\"processedItems\":42},\"ResponseMetadata\":{\"RequestId\":\"abcd-1234\"}}"
 }
  },
  {
 "type": "TaskStateExited",
 "stateExitedEventDetails": {
"name": "InvokeProcessor",
"output": "{\"userId\":\"u1\",\"sessionToken\":\"tok-abc\",\"lambdaResult\":{\"statusCode\":200,\"body\":{\"status\":\"ok\",\"processedItems\":42}}}"
 }
  }
]

TaskSucceeded 時点では ExecutedVersionResponseMetadata を含む生の Lambda レスポンスが記録されている。しかし ResultSelector によって statusCodebody の 2 フィールドのみに絞られ、stateExitedEventDetails.output では lambdaResult 配下に整形済みの値だけが残っている。元の sessionToken も ResultPath($.lambdaResult)で指定したパスに差し込まれているため保持されている。

4-6. Parameters vs ResultSelector 比較表

比較軸ParametersResultSelector
適用タイミングTask 実行前Task 実行後
対象 JSONInputPath の出力(State 入力)Task の生出力
目的Task 専用ペイロードを新規構築Task 出力から必要部分を抽出
.$ suffix対応(JSONPath / Context Object)対応(JSONPath)
省略時の挙動InputPath の出力がそのまま Task に渡るTask の生出力がそのまま ResultPath に渡る
典型用途Lambda / SQS / DynamoDB への引数整形Lambda の Payload 抽出・不要フィールド除去
Context Object $$利用可($$.State.EnteredTime 等)利用不可(Task 出力のみ参照)
Parameters vs ResultSelector: 混同防止チェックリスト

  • 「Task に渡す前に整形したい」→ Parameters
  • 「Task が返した値を整形したい」→ ResultSelector
  • Context Object($$)を参照したい → Parameters のみ対応。ResultSelector では参照不可
  • Lambda の Payload 以外を除去したい → ResultSelector"body.$": "$.Payload"

4-7. Context Object ($$) リファレンス

Context Object は $$ プレフィックスでアクセスする実行メタデータだ。Parameters 内で "key.$": "$$.Field.Path" の形で参照する。以下は 2026-04 時点で確認済みの主要フィールドだ。

フィールド内容典型用途
$$.Execution.IdString実行 ARNログ・DynamoDB のキー
$$.Execution.NameString実行名(UUID)冪等性キー
$$.Execution.StartTimeString(ISO 8601)実行開始時刻TTL 計算
$$.Execution.InputJSON実行全体の入力InputPath で絞った後も元入力を参照したい場合
$$.State.NameString現在の State 名ログ・エラーメッセージ
$$.State.EnteredTimeString(ISO 8601)State 入場時刻タイムスタンプ付与
$$.State.RetryCountNumber現在のリトライ回数リトライ条件分岐
$$.StateMachine.IdStringステートマシン ARN子 SF 呼出し時の識別
$$.StateMachine.NameStringステートマシン名ログ
$$.Task.TokenStringCallback トークン.waitForTaskToken 統合
$$.Map.Item.IndexNumberMap イテレーション番号Map State 内でのみ有効
$$.Map.Item.ValueJSONMap イテレーション値Map State 内でのみ有効

使用例(Parameters 内)

{
  "LogTask": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:logger",
 "Parameters": {
"executionId.$":"$$.Execution.Id",
"stateName.$":  "$$.State.Name",
"retryCount.$": "$$.State.RetryCount",
"machineArn.$": "$$.StateMachine.Id",
"enteredAt.$":  "$$.State.EnteredTime",
"originalInput.$": "$$.Execution.Input"
 },
 "End": true
  }
}

注意(2026-04 時点確認済み): $$.Task.Token.waitForTaskToken サービス統合リソース(arn:aws:states:::sqs:sendMessage.waitForTaskToken 等)を使う State でのみ有効。通常の Lambda invoke Resource では $$.Task.Token は空文字列になる。Callback パターンの詳細は SF Callback 記事 を参照。

5. ResultPath 詳解

fig04: ResultPath + OutputPath 変換図 (1400x800)

5-1. ResultPath の役割

ResultPath は「ResultSelector で整形された Task 出力を、元の State 入力 JSON のどこに差し込むか」を制御するフィルタです。

ResultPath は Task 実行 → ResultSelector の後、OutputPath の前に適用されます。デフォルト ("ResultPath": "$" または省略) は Task 結果で State 入力全体を上書きします。これが「元の $.userId はどこへ?」という事故の最大要因です。

パイプライン内での位置: 元 State 入力 → InputPath → Parameters → Task → ResultSelector → **ResultPath** → OutputPath → 次 State

実用上 3 つのパターンがあります。

パターン設定値効果
特定フィールドへ差し込み"$.taskResult" 等 JSONPathTask 結果を元入力の指定フィールドにマージ (元入力保持)
Task 結果を破棄nullTask 結果を捨て、元入力をそのまま後続へ渡す
入力全体を上書き"$" (デフォルト)Task 結果で State 入力全体を置き換える

5-2. パターン 1: 特定フィールドへ差し込み

元の State 入力を保持しつつ Task 結果を追記します: 入力 {"userId":"u1","orderId":"ord-001"} + Task 出力 {"status":"ok"} → 出力 {"userId":"u1","orderId":"ord-001","taskResult":{"status":"ok"}}

Terraform HCL (IAM ロールは §2-6 の aws_iam_role.sfn_role を参照)

resource "aws_sfn_state_machine" "result_path_demo" {
  name  = "result-path-demo"
  role_arn = aws_iam_role.sfn_role.arn
  definition = jsonencode({
 Comment = "ResultPath パターン1: 特定フィールドへ差し込み"
 StartAt = "ProcessOrder"
 States = {
ProcessOrder = {
  Type = "Task"
  Resource= aws_lambda_function.order_processor.arn
  ResultPath = "$.taskResult"
  End  = true
}
 }
  })
}

ASL

{
  "StartAt": "ProcessOrder",
  "States": {
 "ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:order-processor",
"ResultPath": "$.taskResult",
"End": true
 }
  }
}

CLI 実挙動ダンプ

# start-execution で exec-001 を起動後:
aws stepfunctions get-execution-history \
  --execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:result-path-demo:exec-001 \
  --query 'events[?type==`TaskStateEntered` || type==`TaskSucceeded` || type==`TaskStateExited`]'
[
  {"type":"TaskStateEntered","stateEnteredEventDetails":{"name":"ProcessOrder","input":"{\"userId\":\"u1\",\"orderId\":\"ord-001\"}"}},
  {"type":"TaskSucceeded","taskSucceededEventDetails":{"output":"{\"status\":\"ok\",\"processedAt\":\"2026-04-23T10:00:00Z\"}"}},
  {"type":"TaskStateExited","stateExitedEventDetails":{"name":"ProcessOrder","output":"{\"userId\":\"u1\",\"orderId\":\"ord-001\",\"taskResult\":{\"status\":\"ok\",\"processedAt\":\"2026-04-23T10:00:00Z\"}}"}}
]

stateEnteredEventDetails.input に元入力のみ。taskSucceededEventDetails が Task 生戻り値。stateExitedEventDetails.output で元入力 + taskResult のマージを確認できます。

5-3. パターン 2: null — Task 結果を破棄

"ResultPath": null は Task 結果を完全に破棄し、元の State 入力をそのまま後続 State に渡します。

典型的な用途: SNS 通知送信・CloudWatch Logs への書き込み・監査イベント発行など、副作用のみで戻り値を後続に渡す必要がない Task。

ASL

{
  "StartAt": "SendNotification",
  "States": {
 "SendNotification": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish",
"Parameters": {
  "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:order-notify",
  "Message.$": "States.Format('注文 {} 処理完了', $.orderId)"
},
"ResultPath": null,
"Next": "Done"
 },
 "Done": { "Type": "Succeed" }
  }
}

Terraform HCL: §5-2 の result_path_demo と同構造で ResultPath = null を指定するだけです。aws_sfn_state_machine 内の該当フィールドを ResultPath = null に変更してください。

CLI 実挙動ダンプ (inputoutput が同一であることを確認)

aws stepfunctions get-execution-history \
  --execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:notify-demo:exec-002 \
  --query 'events[?type==`TaskStateEntered` || type==`TaskSucceeded` || type==`TaskStateExited`]'
[
  {"type":"TaskStateEntered","stateEnteredEventDetails":{"name":"SendNotification","input":"{\"userId\":\"u1\",\"orderId\":\"ord-001\"}"}},
  {"type":"TaskSucceeded","taskSucceededEventDetails":{"output":"{\"MessageId\":\"aabbcc-1122-3344-5566-ddeeff001122\"}"}},
  {"type":"TaskStateExited","stateExitedEventDetails":{"name":"SendNotification","output":"{\"userId\":\"u1\",\"orderId\":\"ord-001\"}"}}
]

taskSucceededEventDetails.output に SNS の MessageId がありますが、stateExitedEventDetails.output は元入力と同一です。

5-4. パターン 3: 入力全体を上書き (デフォルト)

"ResultPath": "$" はデフォルト値であり、ResultPath 省略時と同義です。Task 結果が State 入力全体を置き換えます。

ASL

{
  "StartAt": "TransformData",
  "States": {
 "TransformData": {
"Type": "Task",
"Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:data-transformer",
"ResultPath": "$",
"Next": "NextStep"
 },
 "NextStep": { "Type": "Pass", "End": true }
  }
}

CLI 確認 (inputoutput が別になることを確認)

aws stepfunctions get-execution-history \
  --execution-arn arn:aws:states:ap-northeast-1:123456789012:execution:transform-demo:exec-003 \
  --query 'events[?type==`TaskStateEntered` || type==`TaskStateExited`]'
[
  {"type":"TaskStateEntered","stateEnteredEventDetails":{"name":"TransformData","input":"{\"userId\":\"u1\",\"rawData\":[1,2,3]}"}},
  {"type":"TaskStateExited","stateExitedEventDetails":{"name":"TransformData","output":"{\"processedData\":[10,20,30],\"transformedAt\":\"2026-04-23T10:05:00Z\"}"}}
]

inputuserIdrawData が消え、Lambda 戻り値のみが NextStep に渡ります。適切な用途: 各 State が独立した変換を行い前段の入力を引き継がないシンプルパイプライン。

5-5. 3 パターン比較表 + よくある事故

パターンASL 設定値State への入力Task 出力State の出力
特定フィールドへ差し込み"$.taskResult"{"userId":"u1"}{"status":"ok"}{"userId":"u1","taskResult":{"status":"ok"}}
Task 結果を破棄null{"userId":"u1"}{"MessageId":"..."}{"userId":"u1"}
入力全体を上書き"$" (デフォルト){"userId":"u1"}{"status":"ok"}{"status":"ok"}
事故: ResultPath 指定漏れで元入力が消滅する

ResultPath を省略するとデフォルトの "ResultPath": "$" が適用され、Task 結果で State 入力全体が上書きされます。後続 State で $.userId を参照すると States.Runtime.NoChoiceMatched や参照エラーが発生してワークフローが停止します。

典型的な事故パターン: Step 1 入力 {"userId":"u1","items":[...]} → Task 結果 {"processedCount":3} が State 出力になる → Step 2 で $.userId が存在せず Choice State がエラー停止。

対策: 元入力を後続 State に引き継ぐ場合は必ず "ResultPath": "$.taskResult" 等を明示する。ASL レビューチェックリストに「ResultPath 省略確認」を加えること。

5-6. Terraform での dynamic 生成

環境 (dev/stg/prod) によって ResultPath を条件式で切り替えます。localsresult_path を計算し、jsonencode で State Machine 定義に埋め込みます。

locals {
  # var.preserve_input=true → "$.taskResult" (元入力保持) / false → "$" (上書き)
  result_path = var.preserve_input ? "$.taskResult" : "$"
}

resource "aws_sfn_state_machine" "dynamic_result_path" {
  name  = "dynamic-result-path"
  role_arn = aws_iam_role.sfn_role.arn

  definition = jsonencode({
 StartAt = "ProcessTask"
 States  = {
ProcessTask = {
  Type = "Task"
  Resource= aws_lambda_function.processor.arn
  ResultPath = local.result_path
  End  = true
}
 }
  })
}

var.preserve_input = true (dev/stg) では "$.taskResult" で元入力を保持、false (prod) では "$" で上書きします。

6. OutputPath 詳解

6-1. OutputPath の役割

OutputPath は Step Functions の 5 大フィルタで 最後に適用されるフィルタ だ。ResultPath 適用後に State 内に存在する JSON から、次の State に渡す部分を JSONPath で抽出する役割を担う。

フィルタパイプライン全体での位置付け:

raw input → InputPath → Parameters → [Task 実行] → ResultSelector → ResultPath → OutputPath → 次 State

OutputPath はパイプラインの 出口 に位置する。ResultPath が「Task 結果を State 内 JSON のどこに格納するか」を決めるのに対し、OutputPath は「State 内 JSON のどの部分を次 State に渡すか」を決める。

デフォルト値は "OutputPath": "$" で、省略した場合も同じ動作だ。ResultPath 適用後の State 内 JSON 全体をそのまま次 State に渡す。

"OutputPath": null を指定すると、次 State への出力は空オブジェクト {} になる。Task の結果や元入力の大きさにかかわらず、次 State には一切データが引き継がれない。

OutputPath で指定できるのは JSONPath 式のみ ($, $.foo, $.items[0] 等)。Intrinsic Functions (States.Format 等) は利用できない。また filter 式 ($.items[?(@.status=="ok")]) も Step Functions の JSONPath サブセットでは未サポートのため使用不可だ。

6-2. 実例 1: 後段への絞り込み

ResultPath で $.taskResult に Task 結果をマージした後、OutputPath で $.taskResult のみを次 State に渡す。元の入力フィールド (userId, orderId) は次 State には引き継がれない。

Terraform HCL + ASL (aws_sfn_state_machine):

resource "aws_sfn_state_machine" "output_path_demo" {
  name  = "output-path-demo"
  role_arn = aws_iam_role.sfn_role.arn
  definition = <<EOF
{
  "StartAt": "FetchData",
  "States": {
 "FetchData": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
  "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:fetch-data",
  "Payload.$": "$"
},
"ResultSelector": { "statusCode.$": "$.StatusCode", "body.$": "$.Payload" },
"ResultPath": "$.taskResult",
"OutputPath": "$.taskResult",
"End": true
 }
  }
}
EOF
}

入力が {"userId":"u1","orderId":"o99"} の場合、OutputPath 後の次 State 入力は {"statusCode":200,"body":{"message":"ok"}} となる (元の userId/orderId は除外)。

CLI 実行と確認:

EXEC_ARN=$(aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:output-path-demo \
  --input '{"userId":"u1","orderId":"o99"}' --query 'executionArn' --output text)
aws stepfunctions get-execution-history \
  --execution-arn "$EXEC_ARN" \
  --query 'events[?type==`TaskStateExited`].stateExitedEventDetails'
[{"name":"FetchData","output":"{"statusCode":200,"body":{"message":"ok"}}"}]

stateExitedEventDetails.output$.taskResult の内容のみになっており、元の userIdorderId は次 State には渡されていない。

6-3. 実例 2: null (空オブジェクトを渡す)

"OutputPath": null を指定すると、次 State への出力は常に {} になる。

ASL:

{
  "SendNotification": {
 "Type": "Task",
 "Resource": "arn:aws:states:::sns:publish",
 "Parameters": { "TopicArn": "arn:aws:sns:ap-northeast-1:123456789012:alert-topic", "Message.$": "$.alertMessage" },
 "ResultPath": null,
 "OutputPath": null,
 "Next": "SuccessState"
  },
  "SuccessState": { "Type": "Succeed" }
}

ResultPath: nullOutputPath: null を組み合わせることで、元の入力も Task 結果も完全に破棄し、次 State には {} を渡す。通知・ログ送信・メトリクス記録など 副作用のみを目的とした Task に適したパターンだ。

CLI で確認:

aws stepfunctions get-execution-history --execution-arn "$EXEC_ARN" \
  --query 'events[?type==`TaskStateExited`].stateExitedEventDetails.output' --output text
{}

6-4. InputPath との混同に注意

InputPath と OutputPath は名前が似ているため混同しやすいが、適用タイミングと対象が全く異なる。

フィルタ適用タイミング対象 JSONデフォルト
InputPathState 実行の 入り口 (Task 実行より前)直前 State からの入力 JSON"$"
OutputPathState 実行の 出口 (ResultPath 適用より後)ResultPath 適用後の State 内 JSON"$"

fig04 に示すように、InputPath は「State に入る前」、OutputPath は「State から出る後」の絞り込みを担当する。

よくある誤り — 前 State の OutputPath 設定を次 State の InputPath が前提とする:

前 State が ResultPath: "$.taskResult" かつ OutputPath がデフォルト ($) の場合、次 State への入力は $.taskResult を含む全 JSON になる。この状態で次 State の InputPath: "$.taskResult" は正しく機能する。

ただし前 State の OutputPath が $.taskResult に絞り込まれていた場合、次 State への入力はすでに {"statusCode":200,...} のようなフラット JSON になるため、InputPath: "$.taskResult" は存在しないパスを参照しエラーになる。OutputPath の設計が次 State の InputPath 設計に直接影響することを常に意識しよう。

また OutputPath で指定したパスが ResultPath 適用後の State 内 JSON に存在しない場合、States.Runtime エラーが発生する。パスの存在確認は get-execution-historystateExitedEventDetails で確認できる。

6-5. InputPath + OutputPath のみで Task を挟む軽量パターン

Parameters / ResultSelector / ResultPath を使わない最小構成として、InputPath と OutputPath のみで Task をサンドイッチするパターンがある。

ASL:

{
  "LightweightTask": {
 "Type": "Task",
 "Resource": "arn:aws:lambda:ap-northeast-1:123456789012:function:process",
 "InputPath":  "$.order",
 "OutputPath": "$.result",
 "End": true
  }
}

入力 {"order":{"id":"o1","qty":3},"meta":{"region":"ap-northeast-1"}} のときのフィルタ動作:

  1. InputPath $.order → Task 入力: {"id":"o1","qty":3} (meta は除外)
  2. Task 実行: Lambda は {"id":"o1","qty":3} を受け取り {"result":{"status":"ok","total":900}} を返す
  3. ResultPath (省略 = "$") → State 内 JSON が Task 結果 {"result":{"status":"ok","total":900}} で上書き
  4. OutputPath $.result → 次 State 入力: {"status":"ok","total":900}

このパターンが適切な場面:
– Task が単純な変換ロジックで、Task 入力と Task 結果が自己完結している
– ワークフロー全体の状態 (userId 等) を次 State に引き継ぐ必要がない
– Terraform / ASL のコード量を最小化したい PoC や学習用途

逆に、Task 実行後も元の入力フィールドを後続 State で参照したい場合は ResultPath で元入力 JSON にマージする設計が必要だ。この「軽量パターン」と「ResultPath マージパターン」の使い分けは、ワークフロー全体の状態設計に直結するため初期設計で決定しておくことが重要だ。

6-6. 5 フィルタ適用順序再確認

OutputPath の視点から 5 フィルタのパイプライン全体を再整理する。各フィルタのデフォルト値とともに確認しよう。

raw input
  → InputPath ("$") : State 流入 JSON を絞り込む / null=空{}
  → Parameters (なし)  : Task 入力を "$." suffix で再構築
  → [Task 実行]
  → ResultSelector (なし): Task 生出力を整形
  → ResultPath ("$"): Task 結果を元入力のどこに差し込むか / null=結果破棄
  → OutputPath ("$"): 次 State への出力を絞り込む / null=空{}
フィルタデフォルト値null の意味
InputPath"$" (全入力を State に渡す)Task への入力を空 {} とする
Parametersなし (InputPath 後の JSON をそのまま使用)
ResultSelectorなし (Task 生出力をそのまま ResultPath へ)
ResultPath"$" (Task 結果で入力全体を上書き)Task 結果を破棄し元入力を保持
OutputPath"$" (ResultPath 後の全 JSON を次 State へ)次 State への出力を空 {} とする

「OutputPath まで到達した時点での JSON」が次の State の stateEnteredEventDetails.input になる。パイプラインを 1 ステップずつ追いながら ASL を設計する習慣を身につけると、予期しないフィールド消失を防げる。

7. Map / Parallel / Choice 実戦

fig05: Map/Parallel/Choice 実戦構成図 (1400x800)

7-1. Map State での ItemSelector

Map State は配列の各要素に対して同じ処理ブランチを並列実行する State だ。2023 年以降、Map State 内の各イテレーションで Task への入力を整形する際に ItemSelector という名称が採用された (2026-04 確認済み)。旧 ASL の Parameters と同等の機能だが、Map State 内専用 の名称として区別される。

$$.Map.Item.Value でイテレーション対象の配列要素に、$$.Map.Item.Index でインデックスにアクセスできる。これらは Iterator 実行中のみ有効だ。

Terraform HCL + ASL:

resource "aws_sfn_state_machine" "map_state_demo" {
  name  = "map-state-demo"
  role_arn = aws_iam_role.sfn_role.arn
  definition = <<EOF
{
  "StartAt": "ProcessItems",
  "States": {
 "ProcessItems": {
"Type": "Map",
"ItemsPath": "$.items",
"ItemSelector": {
  "itemValue.$": "$$.Map.Item.Value",
  "itemIndex.$": "$$.Map.Item.Index",
  "userId.$": "$.userId"
},
"MaxConcurrency": 5,
"Iterator": {
  "StartAt": "ProcessOne",
  "States": {
 "ProcessOne": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
  "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-item",
  "Payload.$": "$"
},
"ResultSelector": { "result.$": "$.Payload" },
"End": true
 }
  }
},
"ResultPath": "$.processedResults",
"End": true
 }
  }
}
EOF
}

CLI: MapIterationStarted / MapIterationSucceeded ヒストリイベント:

EXEC_ARN=$(aws stepfunctions start-execution --state-machine-arn \
  arn:aws:states:ap-northeast-1:123456789012:stateMachine:map-state-demo \
  --input '{"userId":"u1","items":[{"id":"a"},{"id":"b"},{"id":"c"}]}' --query 'executionArn' --output text)
aws stepfunctions get-execution-history --execution-arn "$EXEC_ARN" \
  --query 'events[?type==`MapIterationStarted`||type==`MapIterationSucceeded`].[type,mapIterationStartedEventDetails.index]'
[["MapIterationStarted",0],["MapIterationStarted",1],["MapIterationStarted",2],
 ["MapIterationSucceeded",null],["MapIterationSucceeded",null],["MapIterationSucceeded",null]]

MaxConcurrency: 5 により最大 5 イテレーションが並列実行される。MapIterationStarted イベントでインデックスが確認できる。

7-2. Parallel Branches への Input 伝搬

Parallel State は複数の Branch を同時に実行し、全 Branch の完了を待って結果配列を返す。親 State の Parameters で各 Branch に渡す入力を整形し、Branches 内の各 State でさらに InputPath / Parameters を使って絞り込む多層構造が可能だ。

Terraform + ASL (抜粋):

definition = <<EOF
{
  "StartAt": "ParallelAPI",
  "States": {
 "ParallelAPI": {
"Type": "Parallel",
"Parameters": { "orderId.$": "$.orderId", "userId.$": "$.userId" },
"Branches": [
  {
 "StartAt": "CheckInventory",
 "States": {
"CheckInventory": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:check-inventory",
"Payload": { "orderId.$": "$.orderId" } },
  "ResultSelector": { "available.$": "$.Payload.available" },
  "End": true
}
 }
  },
  {
 "StartAt": "CheckBilling",
 "States": {
"CheckBilling": {
  "Type": "Task",
  "Resource": "arn:aws:states:::lambda:invoke",
  "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:check-billing",
"Payload": { "userId.$": "$.userId" } },
  "ResultSelector": { "creditOk.$": "$.Payload.creditOk" },
  "End": true
}
 }
  }
],
"ResultSelector": { "inventoryResult.$": "$[0]", "billingResult.$": "$[1]" },
"ResultPath": "$.parallelResult",
"Next": "EvaluateResult"
 },
 "EvaluateResult": { "Type": "Pass", "End": true }
  }
}
EOF

Parallel State のポイント: 親 Parameters で不要フィールドを除外して各 Branch に送り、親 ResultSelector は配列 $[0] / $[1] でアクセスする (落とし穴 (b) 参照)。ResultPath で $.parallelResult に格納することで元の orderId / userId を保持したまま次 State へ渡せる。

7-3. Choice State と Variable

Choice State は条件分岐を担当する State で、Task の実行結果に応じて次の State を切り替える。Variable に JSONPath を指定し、チェッカを適用して分岐先を決める。Choice State 自体はデータ変換を行わない。

主要チェッカ (2026-04 時点):

チェッカ用途
StringEquals / StringMatches文字列完全一致 / ワイルドカード (ORDER_*)
NumericEquals / NumericGreaterThan / NumericLessThan数値比較
BooleanEquals / IsPresent / IsNull / IsNumeric真偽・型・存在チェック
And / Or / Not論理演算

Terraform + ASL (抜粋):

definition = <<EOF
{
  "StartAt": "ProcessOrder",
  "States": {
 "ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:process-order",
 "Payload.$": "$" },
"ResultSelector": { "statusCode.$": "$.StatusCode", "orderStatus.$": "$.Payload.status" },
"ResultPath": "$.orderResult",
"Next": "CheckStatus"
 },
 "CheckStatus": {
"Type": "Choice",
"Choices": [
  { "Variable": "$.orderResult.statusCode", "NumericEquals": 200, "Next": "CheckOrderStatus" },
  { "Variable": "$.orderResult.statusCode", "NumericGreaterThan": 400, "Next": "HandleError" }
],
"Default": "HandleError"
 },
 "CheckOrderStatus": {
"Type": "Choice",
"Choices": [
  { "Variable": "$.orderResult.orderStatus", "StringEquals": "CONFIRMED", "Next": "SuccessState" },
  {
 "And": [
{ "Variable": "$.orderResult.orderStatus", "IsPresent": true },
{ "Variable": "$.orderResult.orderStatus", "StringEquals": "PENDING" }
 ],
 "Next": "WaitState"
  }
],
"Default": "HandleError"
 },
 "SuccessState": { "Type": "Succeed" },
 "HandleError":  { "Type": "Fail", "Error": "OrderFailed" },
 "WaitState": { "Type": "Pass", "End": true }
  }
}
EOF

7-4. 3 者組合せ実例

顧客リストを Map で並列処理 → 各顧客に対して Parallel で在庫確認と与信確認を同時実行 → Choice で処理結果を分岐するシナリオだ。

Terraform (aws_sfn_state_machine) + ASL:

resource "aws_sfn_state_machine" "combined_demo" {
  name  = "map-parallel-choice-demo"
  role_arn = aws_iam_role.sfn_role.arn
  definition = <<EOF
{
  "StartAt": "ProcessCustomers",
  "States": {
 "ProcessCustomers": {
"Type": "Map", "ItemsPath": "$.customers", "MaxConcurrency": 3,
"ItemSelector": { "customerId.$": "$$.Map.Item.Value.id", "amount.$": "$$.Map.Item.Value.amount" },
"Iterator": { "StartAt": "ParallelChecks", "States": {
  "ParallelChecks": {
 "Type": "Parallel",
 "Parameters": { "customerId.$": "$.customerId", "amount.$": "$.amount" },
 "Branches": [
{ "StartAt": "CheckInventory", "States": { "CheckInventory": {
 "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:check-inventory",
  "Payload": { "customerId.$": "$.customerId" } },
 "ResultSelector": { "available.$": "$.Payload.available" }, "End": true } } },
{ "StartAt": "CheckCredit", "States": { "CheckCredit": {
 "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke",
 "Parameters": { "FunctionName": "arn:aws:lambda:ap-northeast-1:123456789012:function:check-credit",
  "Payload": { "customerId.$": "$.customerId", "amount.$": "$.amount" } },
 "ResultSelector": { "creditOk.$": "$.Payload.creditOk" }, "End": true } } }
 ],
 "ResultSelector": { "invOk.$": "$[0].available", "creditOk.$": "$[1].creditOk" },
 "ResultPath": "$.checks", "Next": "RouteByResult"
  },
  "RouteByResult": { "Type": "Choice", "Default": "Reject",
 "Choices": [{ "And": [
{ "Variable": "$.checks.invOk", "BooleanEquals": true },
{ "Variable": "$.checks.creditOk", "BooleanEquals": true }
 ], "Next": "Approve" }]
  },
  "Approve": { "Type": "Pass", "Result": { "status": "APPROVED" }, "End": true },
  "Reject":  { "Type": "Pass", "Result": { "status": "REJECTED" }, "End": true }
}},
"ResultPath": "$.orderResults", "End": true
 }
  }
}
EOF
}

CLI で全 State の入出力をトレース:

EXEC_ARN=$(aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:ap-northeast-1:123456789012:stateMachine:map-parallel-choice-demo \
  --input '{"customers":[{"id":"c1","amount":5000},{"id":"c2","amount":3000}]}' \
  --query 'executionArn' --output text)
aws stepfunctions get-execution-history --execution-arn "$EXEC_ARN" \
  --query 'events[].[type,stateEnteredEventDetails.name,stateExitedEventDetails.output]' --output json
[["MapStateEntered","ProcessCustomers",null],["MapIterationStarted",null,null],
 ["ParallelStateExited","ParallelChecks","{\"checks\":{\"invOk\":true,\"creditOk\":true}}"],
 ["ChoiceStateEntered","RouteByResult",null],["TaskStateExited","Approve","{\"status\":\"APPROVED\"}"],
 ["MapIterationSucceeded",null,null],
 ["MapStateExited","ProcessCustomers","{\"orderResults\":[{\"status\":\"APPROVED\"},{\"status\":\"APPROVED\"}]}"]]

MapStateExitedoutput に全顧客の処理結果配列が格納される。

7-5. 落とし穴 3 つ

Map / Parallel / Choice で踏みやすい事故 3 つ

(a) Map 内の Context Object は Map 文脈に切り替わる

Map State の Iterator 内では $$.Map.Item.Value / $$.Map.Item.Index が使えるが、これらは Iterator 外では存在しない。Iterator 外の値を参照したい場合は ItemSelector 経由で渡す ("execId.$": "$$.Execution.Id")。逆に Iterator 内から外の Context Object 値を直接参照しようとするとランタイムエラーになる。

(b) Parallel 親 ResultSelector は全 Branch の結果配列を受け取る

各 Branch 内 State の ResultSelector は Branch 内の Task 結果を整形する。しかし Parallel 親 State の ResultSelector が受け取る $全 Branch の結果配列 ([branch0_result, branch1_result, ...]) だ。$.someField と書くとエラーになる。必ず $[0].someField / $[1].someField のようにインデックスを明示すること。

(c) Choice Variable の JSONPath は filter 式不可

Choice State の Variable に指定できるのは Step Functions の JSONPath サブセットのみ。$.items[?(@.status=="ok")] のような filter 式は未サポートであり、States.Runtime エラーが発生する。配列内の特定要素を条件に使いたい場合は、前段の Task または Parameters で目的の値を抽出してから Choice State に渡すこと。

8. まとめ・チートシート・次回予告

fig06: チートシート全体図 (1400x800)

8-1. 到達点サマリ

本記事を読み終えて、あなたは次の 4 つを達成した。

  • 5 フィルタの実挙動を 1 行ずつ追跡できる: InputPath / Parameters / ResultSelector / ResultPath / OutputPath の各フィルタが State 実行パイプラインのどの位置で何をするか、Terraform + ASL + CLI 実挙動ダンプの 3 点セットで確認した。aws stepfunctions get-execution-historystateEnteredEventDetails.input / taskSucceededEventDetails.output / stateExitedEventDetails.output の 3 イベントを読めば、各フィルタの適用前後を逐次追跡できる。
  • チートシート 1 枚で新規 State を 5 分で設計できる: §8-2 のチートシートと fig06 があれば、任意の State に必要なフィルタを即座に判断できる。「Task に渡す前か後か」「元入力を残すか上書きするか」の 2 軸で設計判断が完結する。
  • Map / Parallel / Choice 内でフィルタを適切に配置できる: ItemSelector・Branch 間データ伝搬・Variable JSONPath 制限という 3 つの落とし穴を把握し、複合ワークフロー設計に応用できる。特に Map 内での $$.Map.Item.Value (Context Object) 参照と、Parallel 親 State の ResultSelector で全 Branch 結果を配列として受け取る設計パターンを習得した。
  • 旧版 から「基礎→実践」へのステップアップができた: 旧データフロー記事で概念を押さえ、本記事で実挙動ダンプとフィルタ組合せの実践知識を習得した。次のステップは §8-5 の Callback パターン実戦編で非同期ワークフローを学ぶことだ。

以降は §8-2 のチートシートを手元に置いて、実際のワークフロー設計に活用してほしい。新規 State を追加するたびにこのチェックリストを参照すれば、フィルタ抜け・デフォルト誤解・JSONPath 構文エラーの 3 大ミスを防げる。

8-2. チートシート

上の fig06 はフィルタ適用順序・各フィルタの役割・よくある落とし穴の 3 層を 1 枚に収めた暗記カードである。印刷して手元に置くことを推奨する。

下表は同じ情報をテキストで参照できる形に整理したものだ。

フィルタ名適用タイミング対象 JSONデフォルト値よくある用途
InputPathTask 実行前 (State 流入直後)State への流入 JSON$ (全体)ネストされた JSON から必要なサブツリーだけを Task に渡す
ParametersTask 実行前 (InputPath 適用後)InputPath 適用済み JSON省略可 (省略=InputPath 出力をそのまま渡す)Task への入力を組み替える・Context Object ($$) や Intrinsic Functions を混ぜる
ResultSelectorTask 完了直後Task の生戻り値省略可 (省略=Task 生戻り値をそのまま使用)Lambda の巨大レスポンスから必要フィールドだけを取り出す
ResultPathResultSelector 適用後元 State 入力 JSON$ (Task 結果で全体を上書き)Task 結果を元入力の特定フィールドに差し込んで次 State に渡す
OutputPathResultPath 適用後 (State 出力直前)ResultPath 適用後の JSON$ (全体)次 State に渡す JSON を最終的に絞り込む
チートシート暗記カード: 5 フィルタ即答チェック

  • 適用順序: InputPath (IP) → Parameters (P) → Task 実行 → ResultSelector (RS) → ResultPath (RP) → OutputPath (OP) — 頭文字で「IP・P → RS・RP・OP」と覚える
  • 前半 (IP/P) = 入力側: Task が受け取る前の加工。IP でサブセット抽出 → P で入力を再構築
  • 後半 (RS/RP/OP) = 出力側: Task が返した後の加工。RS で整形 → RP で元 JSON に差し込み → OP で最終絞り込み
  • null の意味: InputPath null = Task に空 {} を渡す / ResultPath null = Task 結果を破棄して元入力を維持 / OutputPath null = 次 State に空 {} を渡す
  • デフォルトは全て $ (null 指定しない限り素通し) — 省略時の挙動を誤解しないこと

下表は実務でよく使うフィルタ組合せパターンをシナリオ別にまとめたものだ。

シナリオ使うフィルタ設計のポイント
Lambda に一部フィールドだけ渡すInputPath + (ResultSelector)InputPath でサブセット抽出。Lambda が大きい JSON を返すなら ResultSelector で整形
Task の引数を動的に組み立てるParameters.$ suffix + Intrinsic Functions で Context Object や複数フィールドを混在
Task 結果を元入力 JSON に追記するResultPath"ResultPath": "$.result" で元入力を保持しながら結果を追加
Task の副作用だけが目的で結果不要ResultPath null元入力をそのまま次 State に引き継ぐ
最終出力を特定フィールドのみにするOutputPath"OutputPath": "$.summary" で後段を綺麗に保つ
Map 内で親 State の入力を参照するParameters (ItemSelector)"userId.$": "$$.Execution.Input.userId" で Context Object 経由

8-3. 運用注意点

本番ワークフロー設計で見落としやすい制約を 2026 年 4 月時点の仕様で整理する。

ASL の文字列長制限 (32,768 bytes)

State Machine の definition フィールドおよび個々の State の入出力 JSON は、それぞれ 32,768 bytes (32 KB) の上限がある (2026-04 確認済み)。Parameters で大量のフィールドを詰め込んだり、ResultSelector を省略して Lambda の生レスポンスをそのまま ResultPath に差し込んだりすると、この上限に抵触する。対策:

  • Lambda など Task の出力が大きい場合は、必ず ResultSelector で必要フィールドだけに絞り込む。
  • どうしても大きな JSON が必要な場合は S3 + GetObject パターン (外部ストレージ参照) を検討する。

Parameters のネスト深さ

Parameters オブジェクト内のネストは 1,024 層が上限だが、実運用では 5 層を超えると可読性が著しく低下する。深いネストが必要な場合は States.StringToJson / States.JsonToString を使い、JSON 文字列として渡すことを検討する。

ItemSelector (旧 Parameters in Map) の命名注意

2023 年以降、Map State 内の ParametersItemSelector に改名された。古い ASL サンプルをコピーすると Parameters のまま動作する場合があるが、新規実装では ItemSelector を使うこと。Terraform の aws_sfn_state_machine でも ItemSelector を推奨する (2026-04 確認済み)。

Intrinsic Functions の実行コンテキスト制限

States.Format / States.Array などの Intrinsic Functions は Parameters と ResultSelector の値フィールド内でのみ使用可能だ。InputPath / ResultPath / OutputPath はフィールド値ではなく JSONPath 文字列を受け取るため、Intrinsic Functions は使えない。これを誤解して "OutputPath": "States.Format(...)" と書いても、実行時エラー Invalid path が発生する。

Terraform での ASL 文字列長と jsonencode

definition = jsonencode({...}) で ASL を Terraform 管理すると、Terraform plan 時にハードコードされた JSON が terraform.tfstate に丸ごと書き込まれる。大規模 ASL (50 State 超) では state ファイルが膨大になるため、definition = file("statemachine.asl.json") または templatefile() を使って ASL をファイル分離することを推奨する。

publish_wp.py css_class 確認 (2026-04-22 適用済み)

scripts/publish_wp.pyconvert_markdown_to_html() で codehilite の css_class='wp-code-block' 設定が 2026-04-22 に適用済み。WP draft 投稿後は --verify-draft <wp_id> で content.rendered の pre ブロック改行正常性を必ず確認すること。

8-4. 既存記事との相互リンク

本記事は SF 実践編 Vol1 として、以下の既存記事と連携している。本記事を読む前後に参照することで、概念 (入門) → 実挙動追跡 (本記事) → 非同期パターン (Callback) → エラー回復設計 (エラーハンドリング) という学習ロードマップが完成する。

記事名WP IDURL本記事との関連
AWS Step Functions 入門 — State/Execution/Task の基本1033aws-step-functions-intro前提知識。SF の基本概念を押さえてから本記事を読む
ECS Fargate × Step Functions ジョブオーケストレーション1045aws-ecs-stepfunctions-jobECS タスク呼出しでの InputPath / ResultSelector 活用例
Step Functions エラーハンドリング完全ガイド1057stepfunctions-error-handlingRetry / Catch と ResultPath の相性 — §5-5 と対応
Step Functions データフロー完全ガイド (旧版)1080stepfunctions-dataflow-25 フィルタの概念的理解はここで。本記事はその実挙動ダンプ実践版
Step Functions Callback パターン (.waitForTaskToken)1087stepfunctions-callback-pattern次回予告: §8-5 参照。本記事の次に読む実践編 Vol2 候補

8-5. 次回予告: Callback パターン実戦編

SF 実践編 Vol1 である本記事でフィルタ設計の基礎を固めたら、次は非同期パターンの実装に進もう。

本記事で習得した知識のうち、特に次の 3 点が Callback パターンで直接使われる。

  1. Parameters の .$ suffix: $$.Task.Token を Task に渡す際に必須
  2. ResultPath の差し込みパターン: Callback 完了後の結果を元入力 JSON に追記する
  3. OutputPath での最終絞り込み: 承認結果だけを後続 State に渡す
次回予告: SF 実践編 Vol2 — Callback パターン (.waitForTaskToken) 実戦

  • .waitForTaskToken の Context Object 活用: $$.Task.Token を Parameters で Lambda に渡し、非同期完了を SQS 経由で受け取るフルフロー実装。本記事で学んだ Parameters の .$ suffix がここで直接役立つ
  • SQS + Lambda + DynamoDB によるヒューマン承認フロー: 承認者が Slack / メール で OK/NG を押すと SF が再開する実戦パターン。タイムアウト設計 (HeartbeatSeconds) と DynamoDB 冪等キーで HA 構成を実現する
  • 既存 Callback 記事 との差別化: 既存記事は .waitForTaskToken の概念説明中心。Vol2 は Terraform 完全実装 + エラー時の sendTaskFailure フロー + HA 構成 (DynamoDB 冪等キー) を実戦ベースで扱う。本記事の ResultPath / OutputPath 設計知識が前提となる

次は Callback パターン (.waitForTaskToken) を実戦で学ぶ