Transformación de datos con JSONata en Step Functions - AWS Step Functions

Transformación de datos con JSONata en Step Functions

Con JSONata, obtiene un potente lenguaje de consultas y expresiones de código abierto para seleccionar y transformar los datos de sus flujos de trabajo. Para obtener una breve introducción y una referencia completa sobre JSONata, consulte la documentación de JSONata.org.

En el siguiente vídeo se describen las variables y JSONata en Step Functions con un ejemplo de DynamoDB:

Debe inscribirse para utilizar el lenguaje de consulta y transformación de JSONata para los flujos de trabajo existentes. Al crear un flujo de trabajo en la consola, recomendamos elegir JSONata para la máquina de estado de nivel superior QueryLanguage. Para los flujos de trabajo nuevos o existentes que utilizan JSONPath, la consola ofrece la opción de convertir estados individuales a JSONata.

Tras seleccionar JSONata, los campos del flujo de trabajo pasarán de cinco campos de JSONPath (InputPath, Parameters, ResultSelector, ResultPath y OutputPath) a solo dos campos: Arguments y Output. Además, no utilizará .$ en nombres clave de objetos JSON.

Si es la primera vez que utiliza Step Functions, solo necesita saber que las expresiones de JSONata utilizan la siguiente sintaxis:

Sintaxis de JSONata: "{% <JSONata expression> %}"

Los siguientes ejemplos de código muestran una conversión de JSONPath a 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" } }

Con la entrada { "title" : "Doctor" } y la variable customerName asignadas a "María", ambas máquinas de estado producirán el siguiente resultado de JSON:

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

En el siguiente diagrama, puede ver una representación gráfica que muestra cómo la conversión de JSONPath (izquierda) a JSONata (derecha) reducirá la complejidad de los pasos en sus máquinas de estados:

Diagrama que compara los campos de los estados de JSONPath y JSONata.

Puede (opcionalmente) seleccionar y transformar los datos de la entrada de estado en argumentos para enviarlos a la acción integrada. Con JSONata, puede (opcionalmente) seleccionar y transformar los resultados de la acción para asignarlos a las variables y para obtener el estado Output.

Nota: Los pasos Assign y Output se producen en paralelo. Si decide transformar los datos durante la asignación de variables, esos datos transformados no estarán disponibles en el paso Output. Debe volver a aplicar la transformación de JSONata en el paso Output.

Diagrama lógico de un estado que usa el lenguaje de consulta de JSONata.

Campo QueryLanguage

En las definiciones de ASL de su flujo de trabajo, hay un campo QueryLanguage en el nivel superior de la definición de una máquina de estado y en los estados individuales. Al establecer QueryLanguage dentro de estados individuales, puede adoptar JSONata de forma incremental en una máquina de estado existente en lugar de actualizarla de una sola vez.

El campo QueryLanguage se puede definir en "JSONPath" o "JSONata". Si se omite el campo de nivel superior QueryLanguage, el valor predeterminado es "JSONPath". Si un estado contiene un campo de nivel de estado QueryLanguage, Step Functions utilizará el lenguaje de consulta especificado para ese estado. Si el estado no contiene ningún campo QueryLanguage, utilizará el lenguaje de consulta especificado en el campo de nivel superior QueryLanguage.

Escritura de expresiones JSONata en cadenas JSON

Cuando una cadena del valor de un campo ASL, un campo de objeto JSON o un elemento de matriz JSON esté rodeada de caracteres {% %}, esa cadena se evaluará como JSONata. Tenga en cuenta que la cadena debe empezar con {% sin espacios iniciales y debe terminar con %} sin espacios finales. Si abre o cierra la expresión de forma incorrecta, se producirá un error de validación.

Presentamos algunos ejemplos:

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

  • "Arguments" : {"field1" : "{% $name %}"} en un estado Task

  • "Items": [1, "{% $two %}", 3] en un estado Map

No todos los campos de ASL aceptan JSONata. Por ejemplo, el campo Type de cada estado se debe establecer en una cadena constante. Del mismo modo, el campo Resource del estado Task debe ser una cadena constante. El campo Items del estado Map aceptará una matriz JSON o una expresión JSONata que debe evaluarse en una matriz.

Variable reservada: $states

Step Functions define una única variable reservada llamada $states. En los estados de JSONata, se asignan las siguientes estructuras a $states para su uso en las expresiones de 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 }

Al introducir un estado, Step Functions asigna la entrada de estado a $states.input. El valor de $states.input se puede utilizar en todos los campos que acepten expresiones de JSONata. $states.input siempre hace referencia a la entrada de estado original.

Para los estados Task, Parallel, y Map:

  • $states.result hace referencia al resultado sin procesar de la API o del subflujo de trabajo si se ha realizado correctamente.

  • $states.errorOutput hace referencia a la salida de error si la API o el subflujo de trabajo produjo un error.

    $states.errorOutput se puede usar en Assign u Output del campo Catch.

Al crear, actualizar o validar la máquina de estado se detectarán los intentos de acceso a $states.result o $states.errorOutput en campos y estados que no son accesibles.

El objeto $states.context brinda a sus flujos de trabajo información sobre su ejecución específica, como StartTime, el token de la tarea y la entrada inicial del flujo de trabajo. Para obtener más información, consulte Acceso a los datos de ejecución desde el objeto Context en Step Functions .

Gestión de errores de expresión

En tiempo de ejecución, la evaluación de la expresión de JSONata puede fallar por varios motivos, como los siguientes:

  • Error de tipo: una expresión, como {% $x + $y %}, fallará si $x o $y no es un número.

  • Incompatibilidad de tipo: una expresión podría evaluarse como un tipo que el campo no aceptará. Por ejemplo, el campo TimeoutSeconds requiere una entrada numérica, por lo que la expresión {% $timeout %} fallará si $timeout devuelve una cadena.

  • Valor fuera de rango: una expresión que produzca un valor que se encuentra fuera del rango aceptable de un campo producirá un error. Por ejemplo, una expresión como {% $evaluatesToNegativeNumber %} fallará en el campo TimeoutSeconds.

  • Error de devolución de un resultado: JSON no puede representar una expresión de valor indefinido, por lo que la expresión {% $data.thisFieldDoesNotExist %} generaría un error.

En cada caso, el intérprete generará el error: States.QueryEvaluationError. Los estados Task, Map y Parallel pueden proporcionar un campo Catch para que detecte el error y un campo Retry para volver a intentarlo en caso de error.

Conversión de JSONPath a JSONata

En las siguientes secciones se comparan y explican las diferencias entre el código escrito con JSONPath y JSONata.

No más campos de ruta

ASL requiere que los desarrolladores utilicen versiones Path de los campos, como en TimeoutSecondsPath, para seleccionar un valor de los datos de estado cuando usan JSONPath. Cuando usa JSONata, deja de usar campos Path porque ASL interpreta automáticamente las expresiones de JSONata delimitadas por {% %} para campos que no son de Path, como TimeoutSeconds.

  • Ejemplo heredado de JSONPath: "TimeoutSecondsPath": "$timeout"

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

Del mismo modo, el estado Map ItemsPath se ha reemplazado por el campo Items que acepta una matriz JSON o una expresión de JSONata que debe evaluarse como una matriz.

Objetos JSON

ASL utiliza el término plantilla de carga útil para describir un objeto JSON que puede contener expresiones de JSONPath y valores de campo Parameters y ResultSelector. ASL no utilizará el término plantilla de carga útil para JSONata porque la evaluación de JSONata se realiza para todas las cadenas, independientemente de si aparecen por sí solas o dentro de un objeto JSON o una matriz JSON.

No más .$

ASL requiere que añada “.$” a los nombres de los campos en las plantillas de carga útil para usar JSONPath y las funciones intrínsecas. Al especificar "QueryLanguage":"JSONata", ya no se utiliza la convención “.$” para nombres de campo de objetos JSON. En su lugar, delimita las expresiones de JSONata entre caracteres {% %}. Utiliza la misma convención para todos los campos con valores de cadena, independientemente de la profundidad con la que se encuentre el objeto dentro de otras matrices u objetos.

Campos Arguments y Output

Si QueryLanguage se establece en JSONata, los campos de procesamiento de E/S antiguos se deshabilitarán (InputPath, Parameters, ResultSelector, ResultPath y OutputPath) y la mayoría de los estados tendrán dos campos nuevos: Arguments y Output.

JSONata ofrece una forma más sencilla de realizar transformaciones de E/S en comparación con los campos utilizados con JSONPath. Las características de JSONata hacen que Arguments y Output sean más capaces que los cinco campos anteriores con JSONPath. Estos nuevos nombres de campo también ayudan a simplificar el ASL y a aclarar el modelo para pasar y devolver valores.

Los campos Arguments y Output (y otros campos similares, como los del estado Map ItemSelector) aceptarán un objeto JSON, como:

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

O bien puede usar una expresión de JSONata directamente, por ejemplo:

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

La salida también puede aceptar cualquier tipo de valor JSON, por ejemplo: "Output":true, "Output":42.

Los campos Arguments y Output solo admiten JSONata, por lo que no es válido utilizarlos con flujos de trabajo que usen JSONPath. Por el contrario, InputPath, Parameters, ResultSelector, ResultPath, OutputPath y otros campos de JSONPath solo se admiten en JSONPath, por lo que no es válido utilizar campos basados en rutas cuando se usa JSONata como flujo de trabajo de nivel superior o lenguaje de consulta de estado.

Estado de paso

El Resultado opcional en un estado Pass se trataba anteriormente como el resultado de una tarea virtual. Con JSONata seleccionado como lenguaje de flujo de trabajo o consulta de estado, ahora puede usar el nuevo campo Output.

Estado Choice

Cuando se utiliza JSONPath, los estados de elección tienen una entrada Variable y numerosas rutas de comparación, como la siguiente 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" } ], }

Con JSONata, el estado de elección tiene una Condition en la que puede usar una expresión de JSONata:

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

Nota: Las variables y los campos de comparación solo están disponibles para JSONPath. La condición solo está disponible para JSONata.

Ejemplos de JSONata

Los siguientes ejemplos se pueden crear en Workflow Studio para experimentar con JSONata. Puede crear y ejecutar las máquinas de estado o utilizar el estado Pass para pasar datos e incluso modificar la definición de la máquina de estado.

Ejemplo: entrada y salida

En este ejemplo se muestra cómo utilizar $states.input para usar la entrada de estado y el campo Output para especificar la salida de estado al activa 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 %}" } } } }

Cuando el flujo de trabajo se ejecuta con lo siguiente como entrada:

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

El estado Test o la ejecución de la máquina de estado devolverá la siguiente salida JSON:

{ "lastName": "Last=>Rivera", "orderValue": 27.91 }
Captura de pantalla que muestra la entrada y la salida de un estado sometido a prueba.

Ejemplo: filtrar con JSONata

Puede filtrar sus datos con los operadores Path de JSONata. Por ejemplo, imagine que tiene una lista de productos para introducir y solo quiere procesar productos que contienen cero calorías. Puede crear una definición de máquina de estado con la siguiente ASL y probar el estado FilterDietProducts con la entrada de ejemplo que aparece a continuación.

Definición de máquina de estado para filtrar con JSONata

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

Ejemplo de entrada para la prueba

{ "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" } ] }

Salida de probar el paso en su máquina de estado

{ "dietProducts": [ { "calories": 0, "flavour": "Cola", "name": "Product-2" }, { "calories": 0, "flavour": "Lime", "name": "Product-5" } ] }
Ejemplo de salida para las expresiones de JSONata sometidas a prueba.

Funciones de JSONata proporcionadas por Step Functions

JSONata contiene bibliotecas de funciones para las funciones Cadena, Numérica, Agregación, Booleana, Matriz, Objeto, Fecha/hora y Orden superior. Step Functions proporciona funciones de JSONata adicionales que puede usar en sus expresiones de JSONata. Estas funciones integradas sustituyen a las funciones intrínsecas de Step Functions. Las funciones intrínsecas solo están disponibles en los estados que utilizan el lenguaje de consultas JSONPath.

Nota: Las funciones de JSONata integradas que requieren valores enteros como parámetros redondearán automáticamente hacia abajo los números no enteros proporcionados.

$partition: equivalente en JSONata de una función intrínseca States.ArrayPartition para particionar una matriz grande.

El primer parámetro es la matriz que se va a particionar, el segundo parámetro es un número entero que representa el tamaño de fragmento. El valor de retorno será una matriz bidimensional. El intérprete divide la matriz de entrada en varias matrices del tamaño especificado por el tamaño del fragmento. La longitud del último fragmento de matriz puede ser menor que la longitud de los fragmentos de matriz anteriores si el número de elementos restantes de la matriz es menor que el tamaño del fragmento.

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

$range: equivalente en JSONata de una función intrínseca States.ArrayRange para generar una matriz de valores.

Esta función toma tres argumentos. El primer argumento es un número entero que representa el primer elemento de la nueva matriz, el segundo argumento un número entero que representa el elemento final de la nueva matriz y el tercer argumento es el número entero del valor delta para los elementos de la nueva matriz. El valor de retorno es una matriz de valores recién generada que va desde el primer argumento de la función hasta el segundo argumento de la función, con los elementos intermedios ajustados por el delta. El valor delta puede ser positivo o negativo, lo que incrementará o disminuirá cada elemento desde el último hasta alcanzar o superar el valor final.

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

$hash: equivalente en JSONata de la función intrínseca States.Hash para calcular el valor hash de una entrada determinada.

Esta función toma dos argumentos. El primer argumento es la cadena de origen a la que se va a aplicar la función de hash. El segundo argumento es una cadena que representa el algoritmo de hash para el cálculo del hash. El algoritmo de hash debe tener uno de los siguientes valores: "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512". El valor de retorno es una cadena del hash calculado de los datos.

Esta función se creó porque JSONata no admite de forma nativa la capacidad de calcular hashes.

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

$random: equivalente en JSONata de la función intrínseca States.MathRandom para devolver un número aleatorio n donde 0 ≤ n < 1.

La función toma un argumento entero opcional que representa el valor de inicio de la función aleatoria. Si utiliza esta función con el mismo valor de inicio, devolverá un número idéntico.

Esta función sobrecargada se creó porque la función de JSONata integrada $random no acepta un valor de inicio.

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

$uuid: versión de JSONata de la función intrínseca States.UUID.

La función no toma argumentos. Esta función devuelve un UUID v4.

Esta función se creó porque JSONata no admite de forma nativa la capacidad de generar UUID.

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

$parse: función de JSONata para deserializar cadenas JSON.

La función toma un JSON representado en forma de cadena como único argumento.

JSONata admite esta funcionalidad a través de $eval, sin embargo, $eval no es compatible con los flujos de trabajo de Step Functions.

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