Flattening: Transformación de datos utilizando JSONata

Objetivo Algunos servicios SaaS permiten la creación de campos personalizados. En este caso, se trata de campos de información creados para acompañar solicitudes de atención —también conocidas como tickets. Normalmente, estos campos poseen un título o una descripción; sin embargo, las APIs de estos servicios suelen utilizar identificadores numéricos para referenciarlos.

Ejemplo: Un ticket en Zendesk con campos personalizados contiene una propiedad llamada custom_fields, que incluye una lista de IDs y sus respectivos valores.

"custom_fields": [
  {
    "id": 360032901812,
    "value": "0457000"
  },
  {
    "id": 360032943991,
    "value": "Depto Suporte"
  },
  {
    "id": 360030824791,
    "value": "XDY667"
  }
]

Cada custom field posee un nombre, pero la comunicación vía API utiliza únicamente el ID para referenciar los campos. Para trabajar con datos analíticos, vamos a “traducir” los IDs a sus nombres reales utilizando la siguiente tabla, creada específicamente con ese propósito:

"map": {
  "360032901812": "CEP",
  "360032943991": "Contato Assistência",
  "360030824791": "Cupom"
}

El objetivo es transformar la información original de Zendesk en un objeto que contenga los nombres de las propiedades, como en el siguiente ejemplo:

Registro de Tickets Completo

A continuación se muestra la lista del contenido JSON original que utilizaremos. Observe que existen dos tickets en una lista dentro de la propiedad tickets, y el mapeo está en la propiedad map.

Y al final de la transformación deseamos llegar al siguiente resultado:

Solución

Para resolver la situación, siga los siguientes pasos:

Paso 1: Crear una función que permita encontrar un nombre de custom field basándose únicamente en su ID.

Es decir, al proporcionar un ID, como por ejemplo 360035700992, la función busca e identifica el nombre correspondiente dentro de una lista de mapeo.

Para ello usamos la función $lookup de JSONata, como por ejemplo:

Input
JSONata
Output

Observa que la función $lookup recibe como parámetros una lista y una cadena que contiene el nombre de la propiedad a buscar dentro de dicha lista. La función retorna el valor de la propiedad encontrada.


Paso 2: Mapear cada entrada de un custom field en una propiedad cuyo valor es el mismo valor del custom field

Para esto, utilizaremos la función $map de JSONata. Esta función recibe una lista y permite definir otra función que será ejecutada para cada uno de los elementos de la lista. Es equivalente a un ciclo FOR que ejecutará la función para cada elemento.

Podemos ejecutar esta operación mediante el siguiente código:

Input
JSONata
Output

Observa que la función $map recibió la lista custom_fields como parámetro. Además, especificamos una función mediante la llamada function($v, $i, $a){}. Las variables definidas en esta llamada reciben respectivamente los siguientes valores:

  • $v : elemento del array que está siendo procesado

  • $i : índice del elemento dentro del array

  • $a : el array completo

Usamos estas variables dentro de la función para referenciar lo que necesitamos manipular.

Esta función retorna un objeto por cada elemento de la lista procesada. El contenido de ese objeto está dado por:

Observa que el nombre de la propiedad en la respuesta es exactamente el resultado de la función $lookup cuando buscamos en el objeto map la propiedad cuyo nombre coincide con el valor de la propiedad id del elemento que está siendo procesado ($v.id).

Y el valor de la respuesta es precisamente el contenido de la propiedad value de ese mismo elemento.

En la primera iteración de este $map tendremos:

$v

$i

0

$a

Por lo tanto, el objeto retornado es:

Paso 3: Unir objetos de una lista en un único objeto

Observe que la respuesta de $map en el paso anterior devuelve una lista donde cada objeto corresponde a una propiedad diferente. Ahora necesitamos unir esos objetos en un solo objeto.

Para lograrlo, utilizaremos la función $merge.

En nuestro ejemplo, basta con pasar el resultado de la operación anterior como parámetro de la función $merge. Esta función concatena todas las propiedades de todos los objetos de la lista en un único objeto.

Input
JSONata
Output

Paso 4: Función $flat

Sabemos que esta función del Paso 3 deberá ejecutarse para cada uno de los tickets y, por lo tanto, tendremos que invocarla muchas veces.

Por esa razón, creamos una función que encapsula ese código y que únicamente espera la propiedad custom_fields del ticket para ser ejecutada.

Input
JSONata
Output

Observa que creamos la función $flat definida como:

El código de la función está definido dentro de las llaves, siendo exactamente igual a la función definida en el paso anterior. La única diferencia es que parametrizamos la lista de custom_fields en la variable $fld.

Otra diferencia es que necesitamos envolver el código con paréntesis que engloben todas las especificaciones funcionales. Esto nos permite definir variables y funciones que terminen con punto y coma (;).

Dentro de este bloque programático necesitamos invocar la función para que JSONata ejecute efectivamente el código. Esto se hace con la siguiente línea:

Llamamos a la función pasando la lista definida en custom_fields a la variable $fld. Así obtenemos el objeto que deseábamos.

Finalmente, se observa que usamos líneas de comentarios para documentar el código. Estas líneas tienen delimitadores en el formato /* texto */.


Paso 5: Consolidando los campos mapeados en el objeto de ticket original con la función $merge

En el paso 4 ya generamos un objeto que contiene los campos de custom_fields mapeados. Ahora necesitamos insertar todas las propiedades de este nuevo objeto en el objeto del ticket original.

La mejor forma de hacerlo es utilizando la función $merge. Esta función recibe un array de objetos y consolida todas las propiedades de los objetos individuales en un único objeto.

Sin embargo, necesitamos construir un array que contenga dos objetos: uno con las propiedades originales del ticket y otro con las nuevas propiedades. Podemos hacerlo con la siguiente línea de código:

La función $append, en este caso, anexa dos objetos a una lista, siendo el primer objeto el ticket y el segundo el resultado de nuestra operación $flat, que también devuelve un objeto. Así, la función $append retorna una lista que contiene ambos objetos.

Solo nos falta ejecutar la función $merge sobre la lista generada. JSONata nos permite una sintaxis alternativa en la que indicamos una función que recibe como argumento el resultado del código previamente especificado. Por ejemplo:

En el ejemplo anterior, la función $merge recibe el resultado de la función $append como parámetro de ejecución.

A continuación, se muestra el ejemplo completo para este paso:

Input
JSONata
Output

Observa que la respuesta contiene todas las propiedades originales del ticket, más las propiedades generadas en el mapeo. Más adelante presentaremos una forma de eliminar el campo custom_fields, que se vuelve innecesario después del mapeo.


Paso 6: Procesamiento de múltiples tickets usando $map y la función $all

Hasta el paso 5 consideramos la manipulación de un único ticket, pero podemos recibir múltiples tickets en un solo array y necesitamos procesar cada uno individualmente. Este es exactamente el propósito de la función $map, que será utilizada nuevamente para procesar cada ticket de manera individual.

Haremos esto creando una nueva función llamada $all, cuyo objetivo es procesar todos los tickets, como se define a continuación:

Básicamente, esta función ejecuta el Paso 5 para cada uno de los tickets recibidos en el array tickets.

Existe un pequeño problema con este enfoque que solo se manifiesta cuando recibimos un array vacío de tickets. Tal como está, el resultado sería solo un objeto vacío.

Para garantizar que la ejecución siempre devuelva un array, incluso si está vacío, ejecutamos un $append vacío al final de la función. Este $append es inofensivo cuando la operación devuelve un array de tickets, pero será útil cuando la función devuelva vacío, ya que transformará el objeto vacío en un array vacío.

Con esto, nuestro código completo queda:

Input
JSONata
Output

Paso 7: Eliminando propiedades no deseadas con Transform

Observe que el resultado del Paso 6 todavía contiene algunas propiedades que ya no son necesarias, como custom_fields. Vamos a eliminar esta propiedad, así como sharing_agreement_ids, para ilustrar cómo eliminar propiedades no deseadas.

Utilizamos el operador Transform para realizar este tipo de eliminación. La operación siguiente elimina las propiedades:

Observe que invocamos la función $all y luego enviamos su resultado al operador Transform, que recibe tres argumentos. Los dos primeros $ se refieren al objeto raíz, y la lista siguiente contiene las propiedades que serán eliminadas.

Con esto, obtenemos nuestro resultado final:

Input
JSONata
Output

Last updated