

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 在 Step Functions 中使用 JSONata 轉換資料
<a name="transforming-data"></a>

 使用 JSONata，您可以獲得強大的開放原始碼查詢和表達式語言，以**選取**和**轉換**工作流程中的資料。如需簡介和完整的 JSONata 參考，請參閱 [JSONata.org 文件。 ](https://docs.jsonata.org/overview.html)

**支援的 JSONata 版本**  
Step Functions 支援 JSONata 2.0.6 版。

 下列影片使用 DynamoDB 範例描述 Step Functions 中的變數和 JSONata：




 您必須選擇加入，才能將 JSONata 查詢和轉換語言用於現有工作流程。在主控台中建立工作流程時，我們建議為頂層狀態機器 選擇 JSONata`QueryLanguage`。對於使用 JSONPath 的現有或新工作流程，主控台提供將個別狀態轉換為 JSONata 的選項。

 選取 JSONata 之後，您的工作流程欄位將從五個 JSONPath 欄位 (`InputPath`、`ResultPath`、、 `Parameters` `ResultSelector`和 `OutputPath`) 縮減為僅兩個欄位： `Arguments`和 `Output`。此外，您**不會**`.$`在 JSON 物件金鑰名稱上使用 。

 如果您是 Step Functions 的新手，則只需要知道 JSONata 表達式使用以下語法：

 **JSONata 語法：** `"{% <JSONata expression> %}"`

 下列程式碼範例顯示從 JSONPath 轉換為 JSONata：

```
# Original sample using JSONPath
{
  "QueryLanguage": "JSONPath", // Set explicitly; could be set and inherited from top-level
  "Type": "Task",
  ...
  "Parameters": {
    "static": "Hello",
    "title.$": "$.title",
    "name.$": "$customerName",  // With $customerName declared as a variable
    "not-evaluated": "$customerName"
  }
}
```

```
# Sample after conversion to JSONata
{
  "QueryLanguage": "JSONata", // Set explicitly; could be set and inherited from top-level
  "Type": "Task",
  ...
  "Arguments": { // JSONata states do not have Parameters
    "static": "Hello",
    "title": "{% $states.input.title %}", 
    "name": "{% $customerName %}",   // With $customerName declared as a variable
    "not-evaluated": "$customerName"
  }
}
```

 指定的輸入`{ "title" : "Doctor" }`和`customerName`指派給 的變數`"María"`，兩個狀態機器都會產生下列 JSON 結果：

```
{
  "static": "Hello",
  "title": "Doctor",
  "name": "María",
  "not-evaluated": "$customerName"
 }
```

 在下圖中，您可以看到圖形表示，顯示將 JSONPath （左） 轉換為 JSONata （右） 如何降低狀態機器中步驟的複雜性：

![比較 JSONPath 和 JSONata 狀態欄位的圖表。](http://docs.aws.amazon.com/zh_tw/step-functions/latest/dg/images/compare-jsonpath-jsonata.png)


 您可以 （選擇性） 從狀態輸入選取資料並將其轉換為**引數**，以傳送至您的整合動作。然後，您可以使用 JSONata 從 動作選取並轉換**結果**，以指派給變數和狀態**輸出**。

 注意：**指派**和**輸出**步驟會**平行**進行。如果您選擇在變數指派期間轉換資料，該轉換的資料**將無法**在輸出步驟中使用。您必須在輸出步驟中重新套用 JSONata 轉換。

![使用 JSONata 查詢語言的狀態邏輯圖。](http://docs.aws.amazon.com/zh_tw/step-functions/latest/dg/images/vars-jsonata.png)


## QueryLanguage 欄位
<a name="querylanguage-field"></a>

 在您的工作流程 ASL 定義中，狀態機器定義的最上層和個別狀態都有一個`QueryLanguage`欄位。透過在個別狀態`QueryLanguage`內設定，您可以在現有狀態機器中逐步採用 JSONata，而不是一次全部升級狀態機器。

 `QueryLanguage` 欄位可以設定為 `"JSONPath"`或 `"JSONata"`。如果省略最上層`QueryLanguage`欄位，則預設為 `"JSONPath"`。如果狀態包含狀態層級`QueryLanguage`欄位，Step Functions 將使用該狀態的指定查詢語言。如果狀態不包含`QueryLanguage`欄位，則會使用最上層`QueryLanguage`欄位中指定的查詢語言。

## 以 JSON 字串撰寫 JSONata 表達式
<a name="writing-jsonata-expressions-in-json-strings"></a>

 當 ASL 欄位值中的字串、JSON 物件欄位或 JSON 陣列元素被`{% %}`字元包圍時，該字串將評估為 JSONata 。請注意，字串的開頭必須是`{%`沒有開頭空格的 ，結尾必須是`%}`沒有結尾空格的 。不當開啟或關閉運算式會導致驗證錯誤。

 一些範例：
+  `"TimeoutSeconds" : "{% $timeout %}"` 
+  `"Arguments" : {"field1" : "{% $name %}"}` 處於 `Task` 狀態
+  `"Items": [1, "{% $two %}", 3]` 處於 `Map` 狀態 

 並非所有 ASL 欄位都接受 JSONata。例如，每個狀態`Type`的欄位都必須設定為常數字串。同樣地，`Task`狀態`Resource`的欄位必須是常數字串。`Map` 狀態`Items`欄位將接受 JSON 陣列、JSON 物件或必須評估為陣列或物件的 JSONata 表達式。

## 預留變數 ：$states
<a name="transforming-reserved-variable-states"></a>

 Step Functions 會定義名為 ** `$states` **的單一預留變數。在 JSONata 狀態下，下列結構會指派給 `$states` 以用於 JSONata 表達式：

```
# Reserved $states variable in JSONata states
$states = {
  "input":       // Original input to the state
  "result":      // API or sub-workflow's result (if successful)
  "errorOutput": // Error Output (only available in a Catch)
  "context":     // Context object
}
```

 在狀態項目上，Step Functions 會將狀態輸入指派給 ** `$states.input` **。的值`$states.input`可用於接受 JSONata 表達式的所有欄位。 `$states.input`一律參考原始狀態輸入。

 對於 `Task`、 `Parallel`和 `Map` 狀態：
+  ** `$states.result` ** 如果成功，則是指 API 或子工作流程的原始結果。
+  ** `$states.errorOutput` ** 是指 API 或子工作流程失敗時的錯誤輸出。

   `$states.errorOutput` 可用於 `Catch` 欄位的 `Assign`或 `Output`。

在建立、更新`$states.result`或驗證狀態機器時，將會擷取無法存取它們`$states.errorOutput`的欄位和狀態中嘗試存取 或 。

`$states.context` 物件會提供有關其特定執行的工作流程資訊，例如 `StartTime`、任務字符和初始工作流程輸入。若要進一步了解，請參閱 [從 Step Functions 中的內容物件存取執行資料](input-output-contextobject.md) 。

## 處理表達式錯誤
<a name="handling-errors-jsonata-expressions"></a>

在執行時間，JSONata 表達式評估可能會因為各種原因而失敗，例如：
+  **類型錯誤** - 如果 `$x`或 `$y`不是數字`{% $x + $y %}`，則 等表達式將會失敗。
+  **類型不相容** - 表達式可能會評估為 欄位不接受的類型。例如， 欄位`TimeoutSeconds`需要數值輸入，因此如果 `$timeout`傳回字串，表達式`{% $timeout %}`將會失敗。
+  **值超出範圍 **- 產生超出欄位可接受範圍的值的表達式將會失敗。例如， 等表達式`{% $evaluatesToNegativeNumber %}`會在 `TimeoutSeconds` 欄位中失敗。
+  **無法傳回結果** - JSON 無法代表未定義的值表達式，因此表達式`{% $data.thisFieldDoesNotExist %}`會導致錯誤。
+  **超過記憶體限制** - 在評估期間耗用過多記憶體的 JSONata 表達式將失敗並顯示`Expression evaluation memory limit exceeded`錯誤。這可能會在處理或轉換大量資料的表達式中發生。若要解決此限制，請考慮將資料轉換移至 Lambda 函數。
+  **表達式逾時** - 評估時間超過 1 秒的 JSONata 表達式將會失敗並顯示`Expression evaluation timeout`錯誤。這可能發生在包含無限迴圈或非常昂貴操作的表達式中。
+  **堆疊溢位** - 超過最大遞迴深度的 JSONata 表達式將使用 失敗`Stack overflow error`。如果遞迴未終止，請確保函數具有正確的基本案例或終止條件。如果遞迴終止，但呼叫堆疊太深，請考慮將函數重寫為結尾遞迴，以減少堆疊深度。

在每個案例中，解譯器都會擲回錯誤：`States.QueryEvaluationError`。您的任務、映射和平行狀態可以提供一個`Catch`欄位來捕捉錯誤，以及一個`Retry`欄位來重試錯誤。

## 從 JSONPath 轉換為 JSONata
<a name="converting-from-jsonpath-to-jsonata"></a>

 下列各節會比較並說明使用 JSONPath 和 JSONata 編寫的程式碼之間的差異。

### 沒有更多路徑欄位
<a name="no-more-path-fields"></a>

 ASL 要求開發人員使用 中的欄位`Path`版本`TimeoutSecondsPath`，以便在使用 JSONPath 時從狀態資料中選取值。當您使用 JSONata 時，您不再使用`Path`欄位，因為 ASL 會在非路徑欄位中自動為您解譯 `{% %}`括住的 JSONata 表達式，例如 `TimeoutSeconds`。
+ JSONPath 舊版範例： `"TimeoutSecondsPath": "$timeout"`
+ JSONata： `"TimeoutSeconds": "{% $timeout %}"`

 同樣地，`Map`狀態`ItemsPath`已被接受 JSON 陣列、JSON 物件或必須評估為陣列或物件的 JSONata 表達式`Items`的欄位取代。

### JSON 物件
<a name="json-objects"></a>

 ASL 使用*承載範本*一詞來描述 JSON 物件，該物件可包含 `Parameters`和 `ResultSelector` 欄位值的 JSONPath 表達式。ASL 不會使用 JSONata 的承載範本一詞，因為 JSONata 評估會針對所有字串執行，無論這些字串是自行執行或在 JSON 物件或 JSON 陣列內執行。

### 不再需要 .$
<a name="no-more-"></a>

 ASL 要求您將「`.$`」附加至承載範本中的欄位名稱，以使用 JSONPath 和內部函數。當您指定 時`"QueryLanguage":"JSONata"`，您不再使用 JSON 物件欄位名稱的「`.$`」慣例。反之，您以`{% %}`字元括住 JSONata 表達式。您為所有字串值欄位使用相同的慣例，無論物件巢狀在其他陣列或物件中的深度為何。

### 引數和輸出欄位
<a name="arguments-and-output-fields"></a>

 `QueryLanguage` 當 設為 時`JSONata`，舊的 I/O 處理欄位將會停用 (`InputPath`、`ResultSelector`、 `ResultPath`和 `OutputPath`)`Parameters`，而且大多數狀態都會取得兩個新欄位： `Arguments`和 `Output`。

 相較於搭配 JSONPath 使用的欄位，JSONata 提供更簡單的 I/O 轉換執行方式。JSONata 的功能使用 JSONPath 讓 `Arguments`和 變得比前五個欄位`Output`更強大。這些新的欄位名稱也有助於簡化您的 ASL，並釐清傳遞和傳回值的模型。

 `Arguments` 和 `Output` 欄位 （以及其他類似的欄位，例如`Map`狀態的 `ItemSelector`) 將接受 JSON 物件，例如：

```
"Arguments": {
    "field1": 42, 
    "field2": "{% jsonata expression %}"
}
```

 或者，您可以直接使用 JSONata 表達式，例如：

```
"Output": "{% jsonata expression %}"
```

 輸出也可以接受任何類型的 JSON 值，例如：`"Output":true`、`"Output":42`。

 `Arguments` 和 `Output` 欄位僅支援 JSONata，因此與使用 JSONPath 的工作流程搭配使用是無效的。相反地，`InputPath`、`Parameters`、、、 `ResultSelector` `ResultPath` `OutputPath` 和其他 JSONPath 欄位僅支援 JSONPath，因此使用 JSONata 做為頂層工作流程或狀態查詢語言時，使用路徑型欄位無效。

### 傳遞狀態
<a name="pass-state"></a>

 進入通過狀態的選用**結果**先前視為虛擬任務的*輸出*。選取 JSONata 做為工作流程或狀態查詢語言時，您現在可以使用新的**輸出**欄位。

### 選擇狀態
<a name="choice-state"></a>

 使用 JSONPath 時，選擇狀態具有輸入`Variable`和許多比較路徑，例如下列 `NumericLessThanEqualsPath` ：

```
# JSONPath choice state sample, with Variable and comparison path
"Check Price": {
  "Type": "Choice",
  "Default": "Pause",
  "Choices": [
  {
    "Variable": "$.current_price.current_price",
    "NumericLessThanEqualsPath": "$.desired_price",
    "Next": "Send Notification"
  } ],
}
```

 使用 JSONata 時，選擇狀態具有 ，您可以在`Condition`其中使用 JSONata 表達式：

```
# Choice state after JSONata conversion
"Check Price": {
  "Type": "Choice",
  "Default": "Pause"
  "Choices": [
    {
      "Condition": "{% $current_price <= $states.input.desired_priced %}",
      "Next": "Send Notification"
    } ]
```

 注意：變數和比較欄位僅適用於 JSONPath。條件僅適用於 JSONata。

## JSONata 範例
<a name="jsonata-examples"></a>

 下列範例可在 Workflow Studio 中建立，以實驗 JSONata。您可以建立和執行狀態機器，或使用**測試狀態**來傳入資料，甚至修改狀態機器定義。

### 範例：輸入和輸出
<a name="example-input-and-output"></a>

 此範例顯示當您選擇加入 JSONata 時，如何使用 `$states.input`來使用狀態輸入和 `Output` 欄位來指定狀態輸出。

```
{
  "Comment": "Input and Output example using JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "Basic Input and Output",
  "States": {
    "Basic Input and Output": {
      "QueryLanguage": "JSONata",
      "Type": "Succeed",
      "Output": {
        "lastName": "{% 'Last=>' & $states.input.customer.lastName %}",
        "orderValue": "{% $states.input.order.total %}"
      }
    }
  }
}
```

 當工作流程以下列做為輸入執行時：

```
{
  "customer": {
    "firstName": "Martha",
    "lastName": "Rivera"
  },
  "order": {
    "items": 7,
    "total": 27.91
  }
}
```

測試狀態或狀態機器執行將傳回下列 JSON 輸出：

```
{
  "lastName": "Last=>Rivera",
  "orderValue": 27.91
}
```

![螢幕擷取畫面顯示待測狀態的輸入和輸出。](http://docs.aws.amazon.com/zh_tw/step-functions/latest/dg/images/jsonata-basic-io.png)


### 範例：使用 JSONata 篩選
<a name="example-filtering-with-jsonata"></a>

 您可以使用 JSONata [路徑運算子](https://docs.jsonata.org/path-operators)來篩選資料。例如，假設您有一個產品清單可供輸入，而且您只想處理包含零卡的產品。您可以使用下列 ASL 建立狀態機器定義，並使用下列範例輸入測試`FilterDietProducts`狀態。

 **使用 JSONata 篩選的狀態機器定義** 

```
{
  "Comment": "Filter products using JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "FilterDietProducts",
  "States": {
    "FilterDietProducts": {
      "Type": "Pass",
      "Output": {
        "dietProducts": "{% $states.input.products[calories=0] %}"
      },
      "End": true
    }
  }
}
```

 **測試的範例輸入** 

```
{
  "products": [
    {
      "calories": 140,
      "flavour": "Cola",
      "name": "Product-1"
    },
    {
      "calories": 0,
      "flavour": "Cola",
      "name": "Product-2"
    },
    {
      "calories": 160,
      "flavour": "Orange",
      "name": "Product-3"
    },
    {
      "calories": 100,
      "flavour": "Orange",
      "name": "Product-4"
    },
    {
      "calories": 0,
      "flavour": "Lime",
      "name": "Product-5"
    }
  ]
}
```

 **在狀態機器中測試步驟的輸出** 

```
{
    "dietProducts": [
        {
            "calories": 0,
            "flavour": "Cola",
            "name": "Product-2"
        },
        {
            "calories": 0,
            "flavour": "Lime",
            "name": "Product-5"
        }
    ]
}
```

![測試中 JSONata 表達式的範例輸出。](http://docs.aws.amazon.com/zh_tw/step-functions/latest/dg/images/test-state-jsonata.png)


### 範例：在映射狀態下使用先前的狀態輸出
<a name="example-map-state-with-previous-task-output"></a>

 此範例示範如何使用先前狀態的輸出做為 JSONata 映射狀態的輸入。下列工作流程使用通過狀態來模擬任務，該任務會傳回具有項目清單的順序，然後映射狀態會從該輸出中選取項目陣列，並反覆執行。

 **狀態機器定義** 

```
{
  "Comment": "Example: Using previous state output in a Map state with JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "GetOrder",
  "States": {
    "GetOrder": {
      "Type": "Pass",
      "Output": {
        "orderId": "{% $states.input.orderId %}",
        "items": "{% $states.input.items %}"
      },
      "Next": "ProcessItems"
    },
    "ProcessItems": {
      "Type": "Map",
      "Items": "{% $states.input.items %}",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "CalculateItemTotal",
        "States": {
          "CalculateItemTotal": {
            "Type": "Pass",
            "Output": {
              "name": "{% $states.input.name %}",
              "total": "{% $states.input.price * $states.input.quantity %}"
            },
            "End": true
          }
        }
      },
      "End": true
    }
  }
}
```

 在此定義中，`GetOrder`狀態會輸出不變的訂單資料。Map `ProcessItems` 狀態會使用 從 的輸出`"Items": "{% $states.input.items %}"`中選取`items`陣列`GetOrder`。每個反覆運算會從陣列中接收一個項目，並透過將價格乘以數量來計算項目總計。

 **範例輸入** 

```
{
  "orderId": "ORD-1234",
  "items": [
    {
      "name": "Widget",
      "price": 4.99,
      "quantity": 3
    },
    {
      "name": "Gadget",
      "price": 12.50,
      "quantity": 2
    },
    {
      "name": "Bolt",
      "price": 0.75,
      "quantity": 10
    }
  ]
}
```

 **預期的輸出** 

```
[
  {
    "name": "Widget",
    "total": 14.97
  },
  {
    "name": "Gadget",
    "total": 25
  },
  {
    "name": "Bolt",
    "total": 7.5
  }
]
```

### 範例：平面化平行狀態輸出
<a name="example-flatten-parallel-output"></a>

 當您使用平行狀態時，它會傳回陣列，其中每個元素都是一個分支的輸出。透過 JSONata，您可以使用平行狀態上的 `Output` 欄位，將這些結果扁平化或合併為單一物件。此方法取代 JSONPath `ResultSelector` 欄位。

 下列範例使用具有兩個分支的平行狀態。每個分支使用通過狀態模擬 DynamoDB GetItem 呼叫。平行狀態`$merge($states.result)`會在其`Output`欄位中使用 ，將分支結果合併為單一物件。

 **狀態機器定義** 

```
{
  "Comment": "Example: Flattening Parallel state output with JSONata",
  "QueryLanguage": "JSONata",
  "StartAt": "GetOrderAndCustomer",
  "States": {
    "GetOrderAndCustomer": {
      "Type": "Parallel",
      "Output": "{% $merge($states.result) %}",
      "Branches": [
        {
          "StartAt": "Get Order",
          "States": {
            "Get Order": {
              "Type": "Pass",
              "Output": {
                "orderId": "{% $states.input.orderId %}",
                "orderDate": "2024-11-20",
                "total": 35.99
              },
              "End": true
            }
          }
        },
        {
          "StartAt": "Get Customer",
          "States": {
            "Get Customer": {
              "Type": "Pass",
              "Output": {
                "customerId": "{% $states.input.customerId %}",
                "customerName": "Martha Rivera",
                "email": "martha@example.com"
              },
              "End": true
            }
          }
        }
      ],
      "Next": "Done"
    },
    "Done": {
      "Type": "Succeed"
    }
  }
}
```

 **範例輸入** 

```
{
  "orderId": "12345",
  "customerId": "C-100"
}
```

 **預期的輸出** 

```
{
  "orderId": "12345",
  "orderDate": "2024-11-20",
  "total": 35.99,
  "customerId": "C-100",
  "customerName": "Martha Rivera",
  "email": "martha@example.com"
}
```

 `$merge()` 函數將 物件陣列合併為單一物件。如果分支傳回具有重疊索引鍵的物件，則稍後的陣列元素優先。分支結果的順序一致 — 它們對應於狀態機器定義的`Branches`陣列順序。

## Step Functions 提供的 JSONata 函數
<a name="jsonata-functions-provided-by-sfn"></a>

JSONata 包含字串、數值、彙總、布林值、陣列、物件、日期/時間和高階函數的函數程式庫。Step Functions 提供您可以在 JSONata 表達式中使用的其他 JSONata 函數。這些內建函數可取代 Step Functions 內部函數。內部函數僅適用於使用 JSONPath 查詢語言的狀態。

 注意：需要整數值作為參數的內建 JSONata 函數會自動捨去所提供的任何非整數。

 **$partition -** 等同於`States.ArrayPartition`內部函數的 JSONata，用於分割大型陣列。

 第一個參數是要分割的陣列，第二個參數是代表區塊大小的整數。傳回值將是二維陣列。解譯器會將輸入陣列區塊化為區塊大小所指定大小的多個陣列。如果陣列中剩餘項目的數量小於區塊大小，則最後一個陣列區塊的長度可能會小於先前陣列區塊的長度。

```
"Assign": {
  "arrayPartition": "{% $partition([1,2,3,4], $states.input.chunkSize) %}"
}
```

 **$range** - JSONata 等同於`States.ArrayRange`內部函數以產生值陣列。

 此函數需要三個引數。第一個引數是代表新陣列第一個元素的整數，第二個引數是代表新陣列最後一個元素的整數，第三個引數是新陣列中元素的差異值整數。傳回值是新產生的值陣列，範圍從函數的第一個引數到函數的第二個引數，其中元素在 之間由差異調整。差異值可以是正值或負值，這會從最後一個遞增或遞減每個元素，直到達到或超過結束值為止。

```
"Assign": {
  "arrayRange": "{% $range(0, 10, 2) %}"
}
```

 **$hash** - JSONata 等同於`States.Hash`內部函數，用於計算指定輸入的雜湊值。

 此函數需要兩個引數。第一個引數是要雜湊的來源字串。第二個引數是代表雜湊演算法的字串，用於雜湊計算。雜湊演算法必須是下列其中一個值：`"MD5"`、`"SHA-1"`、`"SHA-256"`、`"SHA-384"`、`"SHA-512"`。傳回值是計算資料雜湊的字串。

 建立此函數是因為 JSONata 原生不支援計算雜湊的能力。

```
"Assign": {
  "myHash": "{% $hash($states.input.content, $hashAlgorithmName) %}"
}
```

 **$random** - JSONata 等同於`States.MathRandom`內部函數，以傳回隨機數字 n，其中 `0 ≤ n < 1`。

 函數會採用*選用*的整數引數，代表隨機函數的種子值。如果您使用此函數搭配相同的種子值，則會傳回相同的數字。

 建立此過載函數是因為內建 JSONata 函數[https://docs.jsonata.org/numeric-functions#random](https://docs.jsonata.org/numeric-functions#random)不接受種子值。

```
"Assign": {
   "randNoSeed": "{% $random() %}",
   "randSeeded": "{% $random($states.input.seed) %}"
}
```

 **$uuid** - JSONata 版`States.UUID`的內部函數。

 函數不採用引數。此函數會傳回 v4 UUID。

 建立此函數是因為 JSONata 原生不支援產生 UUIDs的能力。

```
"Assign": {
  "uniqueId": "{% $uuid() %}"
}
```

 **$parse** - 用於還原序列化 JSON 字串的 JSONata 函數。

 函數採用字串化 JSON 作為其唯一引數。

 JSONata 透過 支援此功能`$eval`；但 Step Functions 工作流程`$eval`不支援 。

```
"Assign": {
  "deserializedPayload": "{% $parse($states.input.json_string) %}"
}
```