

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 在 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 查询和转换语言。在控制台中创建工作流时，建议为顶级状态机 `QueryLanguage` 选择 JSONata。对于使用 JSONPath 的现有工作流或新工作流，控制台提供了将各个状态转换为 JSONata 的选项。

 选择 JSONata 后，您的工作流字段将从五个 JSONPath 字段（`InputPath`、`Parameters`、`ResultSelector`、`ResultPath` 和 `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"
  }
}
```

 给定分配给 `"María"` 的输入 `{ "title" : "Doctor" }` 和变量 `customerName`，两台状态机都将生成以下 JSON 结果：

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

 在下图中，您可以看到一个图形化表示，其中显示将 JSONPath（左）转换为 JSONata（右）会如何降低状态机中步骤的复杂性：

![该图比较了 JSONPath 和 JSONata 状态中的字段情况。](http://docs.aws.amazon.com/zh_cn/step-functions/latest/dg/images/compare-jsonpath-jsonata.png)


 您可以（可选）从状态输入中选择数据，并将其转换为**参数**以发送到您的集成操作。使用 JSONata，您便可（可选）选择并转换操作的**结果**，以分配给变量以及用于状态**输出**。

 注意：**分配**和**输出**步骤**并行**进行。如果您选择在变量赋值期间转换数据，则转换后的数据将**不**可用于“输出”步骤。您必须在“输出”步骤中重新应用 JSONata 转换。

![使用 JSONata 查询语言的状态的逻辑图。](http://docs.aws.amazon.com/zh_cn/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 %}"` 
+  `Task` 状态下的 `"Arguments" : {"field1" : "{% $name %}"}`
+  `Map` 状态下的 `"Items": [1, "{% $two %}", 3]` 

 并非所有 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`。您的 Task、Map 和 Parallel 状态可以提供一个 `Catch` 字段用于捕获错误，并提供一个 `Retry` 字段用于在出错时重试。

## 从 JSONPath 转换为 JSONata
<a name="converting-from-jsonpath-to-jsonata"></a>

 以下各节将对使用 JSONPath 和 JSONata 编写的代码进行比较并阐述两者之间的差异。

### 没有更多路径字段
<a name="no-more-path-fields"></a>

 ASL 要求开发人员在使用 JSONPath 时，使用字段的 `Path` 版本（如 `TimeoutSecondsPath` 中那样）从状态数据中选择一个值。使用 JSONata 时，您将不再使用 `Path` 字段，因为 ASL 会自动在非路径字段（例如 `TimeoutSeconds`）中为您解释 `{% %}` 包裹的 JSONata 表达式。
+ 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 表达式。无论对象嵌套在其他数组或对象中的深度如何，您都对所有字符串值字段使用相同的约定。

### Arguments 和 Output 字段
<a name="arguments-and-output-fields"></a>

 当设置`QueryLanguage`为时`JSONata`，旧的 I/O 处理字段将被禁用（`InputPath`、`Parameters``ResultSelector`、`ResultPath`和`OutputPath`），并且大多数州将获得两个新字段：`Arguments`和`Output`。

 与 jsonPath 中使用的字段相比，JSonata 提供了一种更简单的 I/O 转换方法。JSONata 的功能使得 `Arguments` 和 `Output` 比 JSONPath 之前的五个字段更强大。这些新的字段名称还有助于简化 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 的工作流结合使用是无效的。相反，JSONPath 仅支持 `InputPath`、`Parameters`、`ResultSelector`、`ResultPath`、`OutputPath` 和其他 JSONPath 字段，因此在使用 JSONata 作为顶级工作流或状态查询语言时，使用基于路径的字段是无效的。

### Pass 状态
<a name="pass-state"></a>

 Pass 状态中的可选**结果**以前被视为虚拟任务的*输出*。选择 JSONata 作为工作流或状态查询语言后，您现在可以使用新的 **Output** 字段。

### Choice 状态
<a name="choice-state"></a>

 使用 JSONPath 时，Choice 状态具有输入 `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 时，Choice 状态有一个 `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。Condition 仅可用于 JSONata。

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

 可以在 Workflow Studio 中创建以下示例来试验 JSONata。您可以创建和执行状态机，也可以使用 **Test 状态**来传入数据，甚至修改状态机定义。

### 示例：输入和输出
<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
  }
}
```

Test 状态或状态机执行将返回以下 JSON 输出：

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

![屏幕截图显示了被测状态的输入和输出。](http://docs.aws.amazon.com/zh_cn/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_cn/step-functions/latest/dg/images/test-state-jsonata.png)


### 示例：在 Map 状态下使用之前的状态输出
<a name="example-map-state-with-previous-task-output"></a>

 此示例说明如何使用 jsonata 使用先前状态的输出作为 Map 状态的输入。以下工作流程使用 “通过” 状态来模拟一个任务，该任务返回带有项目列表的订单，然后 Map 状态从该输出中选择项目数组并对其进行迭代。

 **状态机定义** 

```
{
  "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`状态不变地输出订单数据。`ProcessItems`Map 状态`"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>

 当你使用 Parallel 状态时，它会返回一个数组，其中每个元素都是一个分支的输出。使用 jsonata，您可以使用 “并行” 状态下的`Output`字段将这些结果展平或合并到单个对象中。这种方法取代了 jsonPath 字段`ResultSelector`。

 以下示例使用具有两个分支的并行状态。每个分支都使用通过状态来模拟 D GetItem ynamoDB 调用。Parallel 状态在其`Output`字段`$merge($states.result)`中使用来将分支结果合并到单个对象中。

 **状态机定义** 

```
{
  "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 包含字符串、数字、聚合、布尔函数、数组 Date/Time、对象和高阶函数的函数库。Step Functions 提供了其他可在 JSONata 表达式中使用的 JSONata 函数。这些内置函数可以替换 Step Functions 内置函数。内置函数仅适用于使用 JSONPath 查询语言的状态。

 注意：需要整数值作为参数的 Built-in jsonata 函数将自动向下舍入所提供的任何非整数。

 **$partition -** 等效于 `States.ArrayPartition` 内置函数的 JSONata，用于对大型数组进行分区。

 第一个参数是要分区的数组，第二个参数是代表区块大小的整数。返回值将是一个二维数组。解释器将输入数组分成多个数组，其大小由区块大小指定。如果数组中剩余的项目数小于区块大小，则最后一个数组区块的长度可能小于之前的数组区块的长度。

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

 **$range** - 等效于 `States.ArrayRange` 内置函数的 JSONata，用于生成值数组。

 这个函数需要三个参数。第一个参数是代表新数组第一个元素的整数，第二个参数是代表新数组最后一个元素的整数，第三个参数是新数组中元素的增量值整数。返回值是一个新生成的值数组，范围从函数的第一个参数到函数的第二个参数，两者之间的元素根据增量进行调整。增量值可以是正值，也可以是负值，它将从最后一个元素开始依次递增或递减，直至达到或超过最终值。

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

 **$hash** - 等效于 `States.Hash` 内置函数的 JSONata，用于计算给定输入的哈希值。

 这个函数需要两个参数。第一个参数是要进行哈希处理的源字符串。第二个参数是代表用于哈希计算的哈希算法的字符串。哈希算法必须是以下值之一：`"MD5"`、`"SHA-1"`、`"SHA-256"`、`"SHA-384"`、`"SHA-512"`。返回值是计算得出的数据哈希值的字符串。

 之所以创建此函数，是因为 JSONata 本身并不具备计算哈希值的能力。

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

 **$random** - 等效于 `States.MathRandom` 内置函数的 JSONata，用于返回随机数 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** - `States.UUID` 内置函数的 JSONata 版本。

 此函数不接受任何参数。此函数会返回一个 v4 UUID。

 之所以创建此函数，是因为 JSONata 本身并不具备生成 UUID 的能力。

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

 **$parse** - 用于将 JSON 字符串反序列化的 JSONata 函数。

 此函数将字符串化的 JSON 作为其唯一参数。

 JSONata 通过 `$eval` 支持此功能；但是，Step Functions 工作流不支持 `$eval`。

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