Transformer - Conhecendo o JOLT
Descubra mais sobre as transformações com JOLT e saiba como aplicá-las na Digibee Integration Platform.
As aplicações de Transformer (JOLT) permite transformações de JSON através do JOLT.
Para desenvolvimento e teste de todos os exemplos contidos neste artigo, acesse o JOLT playground.
JOLT - JSON
JSON é o formato usado por JOLT em suas transformações. Sua utilização é baseada na estrutura abaixo, sempre dependente de uma entrada:
Onde:
"operations": define o tipo da transformação que será aplicada.
"spec": área para aplicação da transformação.
"[ ]": a própria estrutura base do JOLT também é um JSON, e com isso temos um array que nos permite encadear diversas operations.
Operations
Existem diversos tipo de operations no JOLT.
shift
default
remove
sort
cardinality
modify-default-beta
modify-overwrite-beta
Cada operation tem sua peculiaridade e fará a transformação de maneira diferente. Entretanto, todas elas seguem o mesmo princípio para as transformações, que é a navegação na estrutura do JSON de entrada. Abaixo veremos com mais detalhes como isso ocorre.
shift
Usado para alterar a estrutura de um JSON, mantendo os valores contidos neste mesmoarquivo JSON. Sua utilização consiste em navegar na estrutura do JSON até o campo ou objeto que desejamos pegar seu valor e em seguida informar onde esse valor deve ser colocado no novo JSON que queremos.
Vejamos o exemplo abaixo:
Temos um JSON de entrada contendo informações de Cliente:
E queremos um novo JSON com a seguinte estrutura:
Nossa transformação ficará:
O que fizemos acima foi navegar até os campos de nosso interesse e informar onde os valores de cada um desses campos devem ser inseridos.
Através da notação "." (ponto), conseguimos definir níveis no novo JSON que queremos criar.
Com isso, em "nome":"customer.name"
pegamos o valor do campo nome e jogamos para o campo name dentro do objeto customer, e em "endereco":"customer.address.street"
pegamos o valor do campo endereco e jogamos para o campo street dentro do objeto address que também estará contido no objeto customer.
Podemos pegar um mesmo valor de um campo e jogá-lo para mais de 1 campo ao mesmo tempo.
Em "telefone": ["customer.phoneNumber", "customer.mobileNumber"]
, pegamos o valor do campo telefone e jogamos para phoneNumber e mobileNumber, ambos dentro de customer. Essa abordagem nos permite transpor um único valor para n novos campos.
Nesta operation, apenas os campos manipulados na transformação serão considerados, então qualquer dado no JSON de entrada que não for utilizado será descartado.
JSON final:
default
Usado para adicionar novos campos ou objetos em um JSON, caso esses não existam previamente. Sua utilização consiste em navegar na estrutura do JSON até o nível desejado e inserir o campo ou objeto com seu respectivo valor.
Caso o campo declarado na transformação já exista no JSON de entrada, a transformação não terá efeito.
Vejamos o exemplo abaixo:
Temos um JSON de entrada contendo informações de Cliente:
Entretanto, precisamos de um JSON que além das informações de nome e cpf também contenha a data de nascimento do cliente.
Nossa transformação ficará:
Acima nós navegamos até o objeto cliente e adicionamos o campo dataNascimento com um valor padrão.
remove
Usado para remover campos ou objetos de um JSON. Sua utilização consiste em navegar na estrutura do JSON até o nível desejado e informar o campo a ser removido.
Vejamos o exemplo abaixo:
Temos um JSON de entrada contendo informações de Cliente:
Entretanto, precisamos de um JSON que contenha apenas as informações de nome e cpf do cliente:
Nossa transformação ficará:
O que fizemos acima foi apenas navegar até o campo dataNascimento e atribuí-lo para " " (aspas vazias).
O campo a ser removido deve ser sempre atribuído à uma String vazia(""), caso contrário teremos uma quebra na transformação e a mesma não ocorrerá.
sort
Usado para ordenar campos ou objetos de um JSON em ordem alfabética.
A ordenação dos campos ou objetos não pode ser configurada, portanto todo JSON será afetado. Apenas os nomes dos campos recebem a ordenação e não seus valores.
Vejamos o exemplo abaixo:
Temos um JSON de entrada contendo informações de Funcionários:
Precisamos que todos os campos contidos no JSON sejam ordenados em ordem alfabética:
Para a operation sort, não precisamos definir o objeto spec que sempre usamos junto do campo operation.
Nossa transformação será apenas:
cardinality
Usado para transformarmos campos e objetos simples em objetos listas e vice-versa.
Quando transformamos um objeto lista para um campo ou objeto simples, apenas o primeiro item da lista será considerado.
Vejamos o exemplo abaixo:
Temos um JSON de entrada contendo informações de Produtos:
Precisamos transformar o objeto "produtos" em uma lista:
Nossa transformação será:
Já no caso de termos uma lista de produtos:
E precisarmos transformar essa lista em um objeto simples:
Nossa transformação ficará:
Aprofundando o conhecimento
Até aqui vimos conceitos essenciais para o entendimento e utilização do JOLT, porém antes de conhecermos as operations restantes, precisamos conhecer alguns conceitos mais elaborados.
Ainda como pré-requisito para avançarmos, veremos 2 termos que são utilizados para melhor entendimento sobre JOLT.
São eles:
LHS (Left Hand Side) Usado para referenciar o lado esquerdo da transformação.
RHS (Right Hand Side) Usado para referenciar o lado direito da transformação.
Ou seja, todo conteúdo do JSON que estiver antes dos : (dois pontos), será nosso LHS e o que estiver após os : (dois pontos), será nosso RHS.
Transformação exemplo:
Visto isso, agora podemos avançar.
O grande poder do JOLT está na possibilidade de lidarmos com as transformações de maneira dinâmica. Para isso, utilizamos curingas (wildcards), que são caracteres específicos alocados de diversas maneiras em nossas transformações, cada um com uma função diferente.
Um mesmo curinga pode ter funções diferentes dependendo de sua aplicação (LHS e RHS), e, além disso, podemos combinar diferentes curingas em uma mesma transformação.
Abaixo veremos suas definições e alguns exemplos de aplicações.
&
Usa o conteúdo do que está declarado no LHS para compor a estrutura do JSON de saída, sem a necessidade de explicitar este conteúdo na transformação.
Este curinga usa como base a navegação feita durante a transformação.
Uso: RHS Operations: shift
Exemplo:
Temos um JSON contendo dados de Cliente:
E precisamos desses dados em um objeto de nome "cliente" :
Nossa transformação ficará:
Em &, pegamos os valores dos campos "nome" e "email", e atribuímos para outros campos chamados "nome" e "email" dentro do objeto "cliente" . Dessa forma criamos um novo JSON mantendo a estrutura dos campos do JSON de entrada.
* (asterisco)
Referencia todos os campos e objetos de um JSON sem a necessidade de explicitar seus nomes na transformação.
Uso: LHS Operations: shift, remove, cardinality, modify-default-beta e modify-overwrite-beta
Exemplo:
Temos o JSON de entrada contendo dados de Cliente:
E precisamos desses dados em um objeto de nome "cliente", porém precisamos alterar o campo "documento" para um campo de nome "cpf":
Nossa transformação ficará:
Na linha "*": "cliente.&"
, estamos pegando qualquer conteúdo que exista no JSON de entrada e colocando em um objeto de nome "cliente" mantendo toda a estrutura de qualquer que seja esse conteúdo. Já para o campo "documento", estamos pegando seu valor e atribuindo a um campo de nome "cpf" também dentro do objeto "cliente".
O uso do curinga * junto do & faz com que para cada campo que o * encontre, o & manterá seu nome e valor. Essa utilização combinada de curingas é muito útil pois nos permite manipular um JSON sem a necessidade de conhecer e declarar seu conteúdo.
@
Referencia o valor de um campo ou objeto contido no JSON de entrada, porém possui efeitos diferentes dependendo de sua aplicação.
Uso: LHS e RHS Operations: shift (LHS e RHS), modify-default-beta (RHS) e modify-overwrite-beta (RHS).
Exemplo shift:
Temos um JSON contendo informações de Produto isoladas:
E precisamos agrupá-las em um objeto "produto", relacionando o campo "chave" com o campo "valor":
Nossa transformação será:
Em "@(1,chave)" estamos pegando o valor do campo "chave" para ser usado como nome do campo que receberá o valor do campo "valor" ("@valor"). O uso do @ tanto no LHS quanto no RHS envolve a declaração do nível no qual estamos buscando a informação e fazemos a contagem dos níveis a partir do nível 1.
No caso o campo "chave" se encontra no mesmo nível que o campo "valor", por isso utilizamos o número 1.
A aplicação do @ no LHS segue da mesma forma que no RHS.
Exemplos modify-default-beta e modify-overwrite-beta:
Temos um JSON de entrada com dados de Produto:
E precisamos que o objeto "produto" contenha um campo "empresa" com o valor do campo "fabricante", que está fora de "produto":
A transformação ficará:
O que fizemos foi criar um campo de nome "empresa" e atribuir nele o valor do campo "fabricante". Para isso subimos até o nível 2 para poder enxergar o campo "fabricante" e assim pegar seu valor.
A diferença entre modify-default-beta e modify-overwrite-beta é que na modify-default-beta a inclusão do campo "empresa" só é feita caso não exista outro campo de nome "empresa" dentro de "produto".
Já para a modify-overwrite-beta a inclusão do campo empresa será feita mesmo que já exista o campo "empresa" dentro de "produto". Como o próprio nome overwrite sugere, caso o conteúdo que queremos adicionar já exista no JSON de entrada, ele sempre será sobrescrito.
$
Referencia o nome de um campo ou objeto contido no JSON de entrada para ser usado como valor de um campo ou objeto no JSON de saída.
Uso: LHS Operations: shift
Exemplo:
Temos um JSON de entrada com informações de Produto:
E precisamos de um JSON para saber quais informações de Produto estão sendo fornecidas:
Nossa transformação será:
O que fizemos foi selecionar todos (*) os campos do objeto "produto", em seguida pegar o nome ($) de cada um deles e atribuí-los a uma lista de nome "produto".
Dessa forma podemos obter o nome de cada campo e não seus valores.
#
Se usado no LHS, tem a função de inserir valores manualmente no JSON de saída.
Já no RHS, é aplicável apenas para a criação de listas e tem a função de agrupar determinado conteúdo do JSON de entrada dentro da lista a ser criada.
Uso: LHS e RHS Operations: shift
Exemplo LHS:
Temos um JSON de entrada com informações de Produto:
E precisamos de um JSON que contenha informações de nome, valor, peso e categoria do produto:
Porém o JSON de entrada nunca fornece informações de categoria, com isso precisamos adicionar manualmente esse campo:
O valor contido após o curinga # será sempre atribuído ao campo declarado no RHS, que no nosso caso é o campo "categoria" dentro do objeto "produto".
Exemplo RHS:
Temos um JSON de entrada contendo uma lista de Produtos:
E precisamos apenas alterar o nome do campo "valor" para "preco":
Nossa transformação ficará:
O uso do # no RHS envolve a declaração do nível no qual estamos buscando a informação. A declaração [#2] representa a criação de uma lista ([ ]) e que esta deve agrupar (#) todas as informações que forem encontradas 2 níveis acima.
Precisamos dessa declaração para que seja garantido o agrupamento correto de cada produto com seu respectivo "codigo" e "preco".
Ou seja, em "codigo": "produtos[#2].&"
estamos pegando o valor do campo "codigo" e jogando para outro campo "codigo" dentro de uma lista de nome "produtos", e em "valor": "produtos[#2].preco"
pegamos o valor do campo "valor" e jogamos para um campo de nome "preco" dentro da mesma lista "produtos".
Porém no momento da criação da lista "produtos" olharemos 2 níveis acima (nivel da lista "listaProdutos") e da maneira que cada item estiver agrupado na "listaProdutos" anterior, esse agrupamento será mantido na nova lista "produtos".
| (pipe)
Permite referenciar múltiplos campos ou objetos de um JSON de entrada para que, independente do nome do campo ou objeto, seu valor seja alocado no mesmo destino no JSON de saída.
Uso: LHS Operations: shift
Exemplo:
Temos um JSON de entrada contendo dados de Cliente:
E precisamos de um JSON da seguinte maneira:
Entretanto, no JSON de entrada, existe a possibilidade do campo "nomeCompleto"
vir com o nome "nomeCliente".
Com isso precisamos que a transformação esteja preparada para reconhecer as duas possibilidades:
Operations modify-default-beta e modify-overwrite-beta
Como dito na explicação do curinga @, estas operations nos permitem referenciar valores de maneira dinâmica. Enquanto a modify-default-beta atribuirá um valor a um campo caso este não exista, a modify-overwrite-beta sobrescreverá qualquer qualquer valor mesmo que o campo já exista.
Entretanto, a modify-overwrite-beta nos permite também aplicar funções em nosso JSON.
São elas:
String toLower, toUpper, concat, join, split, substring, trim, leftPad e rightPad
Number min, max, abs, avg, intSum, doubleSum, longSum, intSubtract, doubleSubtract, longSubtract, divide e divideAndRound
Type toInteger, toDouble, toLong, toBoolean, toString, recursivelySquashNulls, squashNulls, size
List firstElement, lastElement, elementAt, toList, sort
Exemplos:
JSON de entrada:
Transformações:
JSON de saída:
Algumas funções não foram incluídas na transformação acima pois seguem o mesmo princípio de outras que foram inclusas, como por exemplo as funções doubleSum e longSum que são aplicadas da mesma forma que a intSum. Referente às funções recursivelySquashNulls e squashNulls, ambas são aplicáveis apenas em objetos e listas e servem para removermos campos com valores nulos, porém enquanto a recursivelySquashNulls olhará para todos os níveis abaixo do objeto ou lista, a squashNulls olhará apenas 1 nível abaixo.
A modify-overwrite-beta tem sua execução em cascata, ou seja, cada nova transformação é impactada pelas transformações anteriores.
Para entendermos esse comportamento em cascata, vamos pegar um trecho do JSON de entrada e das transformações acima:
Em "produto": "=toLower(@(1,produto))"
mudamos o valor de "produto" para minúsculo e em "empresa": "=toUpper(@(1,empresa))"
, mudamos o valor de "empresa" para maiúsculo.
Portanto no momento em que fazemos "produto_empresa": "=concat(@(1,produto),'_',@(1,empresa))"
, estamos utilizando os valores de "produto" e "empresa" já transformados e não seus valores originais contidos no JSON de entrada e com isso teremos "produto_empresa": "produto a_EMPRESA A"
.
Para uma leitura mais técnica a respeito de JOLT, acesse a documentação sobre JOLT no GitHub.
Atualizado