Flattening: Transformação de dados utilizando JSONata
Objetivo
Alguns serviços SaaS permitem a criação de campos personalizados. No caso em questão, trata-se de campos de informação criados para acompanhar chamados de atendimento - também conhecidos como tickets. Normalmente, esses campos possuem campos de título ou descrição, porém, geralmente as APIs desses serviços utilizam identificadores numéricos para referenciá-los.
Exemplo: Um ticket no Zendesk com campos personalizados contém uma propriedade chamada custom_fields contendo uma lista de ids e seus valores.
Cada custom filed possui seu nome, mas a comunicação por API utiliza apenas o ID para referenciar os campos. Para trabalharmos com dados analíticos vamos "traduzir" os ID para os seus nomes verdadeiros utilizando a tabela abaixo, que foi construída apenas para esse propósito:
O objetivo é transformar a informação original do Zendesk em um objeto contendo os nomes das propriedades, como no exemplo abaixo:
Registro de Tickets Completo
Segue abaixo a listagem do conteúdo JSON original que utilizaremos. Observe que existem dois tickets em uma lista na propriedade tickets e o mapeamento está na propriedade map.
E no final da transformação desejamos chegar ao seguinte resultado:
Solução
Para solucionar a situação, siga os seguintes passos:
Passo 1: Criar uma função que permita encontrar um nome de custom field baseado-se apenas no seu ID.
Ou seja, ao fornecer um ID, como por exemplo 360035700992
, a função busca e identifica o nome correspondente dentro de uma lista de mapeamento.
Para isso usamos a função $lookup do JSONata, como por exemplo:
"CEP"
Observe que a função $lookup recebe como parâmetros uma lista e uma string que contém o nome da propriedade a ser buscada na lista. A função retorna o valor da propriedade encontrada.
Passo 2: Mapear cada entrada de um custom field em uma propriedade cujo valor é o mesmo valor do custom field.
Conseguimos executar essa operação através do código abaixo:
Observe que a função $map recebeu a lista custom_fields como parâmetro. Além disso, especificamos uma função através da chamada function($v, $i, $a){}. As variáveis especificadas nesta chamada recebem respectivamente os seguintes valores:
$v : elemento do array que está sendo processado
$i : indice do elemento no array
$a : o array inteiro
Utilizamos as variáveis acima dentro da função para referenciar o que precisamos processar.
Essa função retorna um objeto para cada elemento da lista que é processado. O conteúdo desse objeto é dado por:
Na primeira interação desse $map teremos:
$v
$i
0
$a
Portanto o objeto retornado é:
Passo 3: Juntar objetos de uma lista em um único objeto
Observe que a resposta do $map no passo anterior retorna uma lista onde cada objeto é uma propriedade diferente. Precisamos agora juntar esses objetos de uma lista em um único objeto. Para isso, vamos utilizar a função $merge.
No nosso exemplo, basta passar o resultado da operação anterior como parâmetro da função $merge. Essa função concatena todas as propriedades de todos objetos na lista em um único objeto.
Passo 4: Função $flat
Sabemos que essa função do passo 03 deverá ser executada para cada um dos tickets e, portanto, teremos que invocá-la muitas vezes. Por essa razão, criamos uma função que acomoda esse código e espera apenas a propriedade custom_fields do ticket para ser executada.
Observe que criamos a função $flat definida como:
O código da função está definido dentro das chaves sendo exatamente igual à função definida no passo anterior. A única diferença é que parametrizamos a lista de custom_fields na forma da variável $fld.
Outra mudança observada é que precisamos envolver o código com parênteses que englobam todas as especificações funcionais. Isso nos permite definir variáveis e funções que terminam com ponto e vírgula (;).
Dentro desse bloco programático precisamos invocar a função para que o JSONata efetivamente execute o código. É exatamente isso que fazemos com a seguinte linha:
Chamamos a função passando a lista definida em custom_field na variável $fld. Com isso recebemos o objeto que desejávamos.
Finalmente nota-se que utilizamos linhas de comentários para documentar o código. Essas linhas possuem delimitadores no formato /* texto */.
Passo 5: Consolidando os campos mapeados no objeto de ticket original com a função $merge
Já geramos no passo 4 um objeto contendo os campos do custom_fields já mapeados. Precisamos agora inserir todas as propriedades desse novo objeto no objeto do ticket original. A melhor forma de fazer isso é utilizando a função $merge. Essa função recebe um array de objetos e consolida todas as propriedades dos objetos individuais em um único objeto.
Entretanto, precisamos construir um array que contenha dois objetos, um com as propriedades originais do ticket e outro com as novas propriedades. Conseguimos fazer isso com a seguinte linha de código:
A função $append neste caso anexa dois objetos à uma lista, sendo que primeiro objeto é o tickets e o segundo objeto é o resultado da nossa operação $flat que também retorna um objeto. Nesse caso a função $append retorna uma lista contendo os dois objetos mencionados.
Nos falta apenas executar a função $merge sobre a lista gerada. O JSONata nos permite uma sintaxe alternativa na qual indicamos uma função que recebe como argumento o resultado do código previamente especificado. Segue o exemplo abaixo:
No exemplo acima, a função $merge recebe o resultado da função $append como parâmetro de execução. Segue abaixo o exemplo completo para este passo:
Observe que a resposta contém todas as propriedades originais do ticket, mais as propriedades geradas no mapeamento. Mais adiante apresentaremos uma forma de remover o campo custom_fields que tornou-se desnecessário depois do mapeamento.
Passo 6: Processamento de múltiplos tickets usando $map e a função $all Até o passo 5 consideramos a manipulação de um único ticket, porém recebemos múltiplos tickets em um único array e precisamos processar cada um dos tickets individualmente. Esse é exatamente o propósito da função $map que será utilizada mais uma vez para processar cada um dos tickets individualmente.
Faremos isso criando uma nova função chamada $all cujo objetivo é processar todos os tickets, como definido abaixo:
Basicamente essa função executa o passo 5 para cada um dos tickets recebidos no array tickets.
Existe apenas um problema na abordagem acima que se manifesta apenas quando recebemos um array vazio de tickets. Na forma como está acima, o resultado seria apenas um objeto vazio. Para garantir que essa execução sempre retorne um array, mesmo que vazio, executamos um $append vazio na sequência da função. Esse $append é inócuo quando a operação resultar um array de tickets, mas será útil quando a função retornar vazio, pois transformará o objeto vazio em um array vazio.
Com isso, nosso código fica:
Passo 7: Removendo propriedades indesejadas com o Transform
Observe que o resultado no passo 06 ainda exige algumas propriedades que não são mais desejadas, como o custom_fields. Vamos remover essa propriedade assim como o sharing_agreements_ids para ilustrar como remover propriedades indesejadas.
Observe que invocamos a função $all e depois submetemos o resultado dela ao operador Transform que recebe três argumentos. Os dois primeiros $ referem-se ao objeto raiz e a lista seguinte possui as propriedades que são removidas.
Com isso, chegamos ao nosso resultado final:
Last updated