在 Step Functi JSONata ons 中使用转换数据 - AWS Step Functions

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

在 Step Functi JSONata ons 中使用转换数据

借 JSONata助,您可以获得强大的开源查询和表达式语言,用于在工作流程中选择转换数据。有关简要介绍和完整 JSONata 参考资料,请参阅 JSONata.org 文档

以下视频描述了变量,并 JSONata 在 Step Functions 中以 DynamoDB 为例:

您必须选择使用现有工作流程的 JSONata 查询和转换语言。在控制台中创建工作流程时,我们建议选择 JSONata 顶级状态机QueryLanguage。对于使用的现有工作流程或新工作流程 JSONPath,控制台提供了将单个状态转换为的选项 JSONata。

选择后 JSONata,您的工作流程字段将从五个 JSONPath 字段(InputPathParametersResultSelectorResultPath、和OutputPath)减少到只有两个字段:ArgumentsOutput。此外,您不会在 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 状态中的字段的示意图。

您可以(可选)从状态输入中选择数据,并将其转换为参数以发送到您的集成操作。然后 JSONata,您可以(可选)选择并转换操作的结果,以分配给变量和状态输出

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

使用 JSONata 查询语言的状态的逻辑图。

QueryLanguage 字段

在您的工作流 ASL 定义中,有一个位于状态机定义顶级和各个状态中的 QueryLanguage 字段。通过QueryLanguage在单个状态内进行设置,可以在现有状态机 JSONata 中逐步采用,而不是一次性升级所有状态机。

QueryLanguage 字段可设置为 "JSONPath""JSONata"。如果省略顶级 QueryLanguage 字段,则默认为 "JSONPath"。如果状态包含状态级 QueryLanguage 字段,则 Step Functions 将使用该状态的指定查询语言。如果该状态不包含 QueryLanguage 字段,则它将使用顶级 QueryLanguage 字段中指定的查询语言。

用 JSON 字符串编写 JSONata 表达式

当 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

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总是指原始状态输入。

对于 TaskParallelMap 状态:

  • $states.result 指 API 或子工作流成功时的原始结果。

  • $states.errorOutput 指 API 或子工作流失败时的错误输出。

    $states.errorOutput 可用于 Catch 字段的 AssignOutput

如果在无法访问 $states.result$states.errorOutput 的字段和状态中尝试对其进行访问,则在创建、更新或验证状态机时将捕获该尝试。

$states.context 对象为您的工作流提供有关其具体执行的信息,例如 StartTime、任务令牌和初始工作流输入。要了解更多信息,请参阅在 Step Functions 中从上下文对象访问执行数据

处理表达式错误

在运行时, JSONata 表达式求值可能由于多种原因而失败,例如:

  • 类型错误 - 如果 $x$y 不是数字,则表达式(例如 {% $x + $y %})将失败。

  • 类型不兼容 - 表达式的计算结果可能为该字段不接受的类型。例如,字段 TimeoutSeconds 需要数字输入,因此如果 $timeout 返回字符串,则表达式 {% $timeout %} 将失败。

  • 值超出范围 - 生成超出字段可接受范围的值的表达式将失败。例如,诸如 {% $evaluatesToNegativeNumber %} 之类的表达式在 TimeoutSeconds 字段中将会失败。

  • 未能返回结果 - JSON 不能表示未定义的值表达式,因此表达式 {% $data.thisFieldDoesNotExist %} 会导致错误。

在每种情况下,解释器都会引发错误:States.QueryEvaluationError。您的 Task、Map 和 Parallel 状态可以提供一个 Catch 字段用于捕获错误,并提供一个 Retry 字段用于在出错时重试。

从 JSONPath 转换为 JSONata

以下各节比较并解释了使用 JSONPath 和编写的代码之间的区别 JSONata。

没有更多路径字段

ASL 要求开发人员在使用时使用 JSONPath字段Path版本从状态数据中选择值,如中所TimeoutSecondsPath示。使用时 JSONata,您将不再使用Path字段,因为 ASL 会自动为您解释非路径字段中{% %}包含的 JSONata 表达式,例如。TimeoutSeconds

  • JSONPath 旧版示例:"TimeoutSecondsPath": "$timeout"

  • JSONata : "TimeoutSeconds": "{% $timeout %}"

同样,Map状态ItemsPath已替换为接受 JSON 数组、JSON 对象或必须计算为数组或对象的 JSONata 表达式的Items字段。

JSON 对象

ASL 使用术语有效载荷模板来描述可以包含ParametersResultSelector字段值 JSONPath 表达式的 JSON 对象。ASL 不会使用术语有效负载模板, JSONata 因为所有字符串都会 JSONata 进行评估,无论它们是单独出现的,还是出现在 JSON 对象或 JSON 数组中。

没有更多 .$

ASL 要求您在要使用的有效载荷模板 JSONPath 和内部函数中的字段名称后附加 .$ “”。指定 "QueryLanguage":"JSONata" 时,您不再对 JSON 对象字段名称使用“.$”约定,而是用{% %}字符将 JSONata 表达式括起来。无论对象嵌套在其他数组或对象中的深度如何,您都对所有字符串值字段使用相同的约定。

Arguments 和 Output 字段

当设置QueryLanguage为时JSONata,旧的 I/O 处理字段将被禁用(InputPathParametersResultSelectorResultPathOutputPath),并且大多数州将获得两个新字段:ArgumentsOutput

JSONata 与使用的字段相比,提供了一种更简单的 I/O 转换方法。 JSONPath JSONata的功能比前五个字段Output更强大 JSONPath。Arguments这些新的字段名称还有助于简化 ASL,并阐明值传递和返回模型。

ArgumentsOutput 字段(以及其他类似字段,如 Map 状态的 ItemSelector)将接受 JSON 对象,例如:

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

或者,你可以直接使用 JSONata 表达式,例如:

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

输出也可以接受任何类型的 JSON 值,例如:"Output":true"Output":42

ArgumentsOutput字段仅支持 JSONata,因此在使用的工作流程中使用它们是无效的 JSONPath。相反,InputPathParametersResultSelectorResultPathOutputPath、和其他 JSONPath 字段仅在中受支持 JSONPath,因此在 JSONata 用作顶级工作流程或状态查询语言时,使用基于路径的字段是无效的。

Pass 状态

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

Choice 状态

使用时 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,选择状态有一个你可以使用 JSONata 表达式Condition的地方:

# 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 例子

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

示例:输入和输出

此示例说明在您选择加入时$states.input如何使用状态输入和Output字段来指定状态输出 JSONata。

{ "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 }
屏幕截图显示了被测状态的输入和输出。

示例:使用筛选 JSONata

您可以使用 JSONata 路径运算符筛选数据。例如,假设您有一份可供输入的产品清单,而您只想处理卡路里含量为零的产品。您可以使用以下 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 表达式的输出示例。

JSONata Step Functions 提供的函数

JSONata 包含字符串、数字、聚合、布尔函数、数组、对象、日期/时间和高阶函数的函数库。Step Functions 提供了可以在 JSONata 表达式中使用的其他 JSONata 函数。这些内置函数可以替换 Step Functions 内置函数。内部函数仅在使用 JSONPath 查询语言的状态下可用。

注意:需要整数值作为参数的内置 JSONata 函数会自动向下舍入所提供的任何非整数。

$partiti JSONata on- 等同于对大型数组进行分区的States.ArrayPartition内在函数。

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

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

$rang e- 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 等同于返回随机数 n 的States.MathRandom内在函数,其中。0 ≤ n < 1

该函数接受一个可选的整数参数,表示随机函数的种子值。如果您使用具有相同种子值的函数,它将返回相同的数字。

之所以创建这个重载函数,是因为内置 JSONata 函数$random不接受种子值。

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

$uuid-States.UUID 内部函数的 JSONata 版本。

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

之所以创建此函数,是因为 JSONata 它本身不支持生成 UUIDs功能。

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

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

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

JSONata 通过支持此功能$eval;但是,Step Fun $eval ctions 工作流程不支持此功能。

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