Comparar JSON

Nada do que você digita aqui é armazenado, registrado ou compartilhado.

Cole dois documentos JSON para ver cada valor adicionado, removido e alterado listado pelo caminho. A ordem das chaves é ignorada, então só diferenças reais aparecem.

Como usar

  1. Cole o JSON original à esquerda.
  2. Cole o JSON que quer comparar à direita.
  3. As diferenças aparecem abaixo, cada uma com seu caminho completo.

Como a comparação funciona

Os dois documentos são analisados e percorridos em paralelo a partir da raiz, comparando o que houver no mesmo caminho de cada lado. Objetos são pareados por chave, então reordenar chaves não muda nada; arrays são pareados por índice. Cada diferença é informada como um de três tipos: adicionado, quando o caminho só existe à direita; removido, quando só existe à esquerda; e alterado, quando os dois lados têm valor naquele caminho, mas os valores diferem.

Uma mudança de tipo conta como alteração, e vale olhar com atenção quando aparece. Um campo que era o número 42 e agora é a string "42" costuma continuar funcionando num cliente tolerante e depois falhar em algo mais adiante que faz contas com ele. O mesmo vale para um campo que era um objeto e virou um array com um objeto, um acidente comum em código que monta respostas de forma condicional.

Por que arrays são comparados por índice

Esta é a decisão de projeto que mais afeta o que você vê, então vale deixar claro. Insira um elemento no início de um array de 200 elementos e uma comparação por índice informa 200 alterações, porque todo elemento depois do ponto de inserção agora está num índice diferente. Não há nada errado; o diff só está respondendo a uma pergunta diferente da que você tinha em mente.

A alternativa é buscar a maior subsequência comum e informar a inserção, que é o que um diff de linhas faz com texto. Para JSON isso não é obviamente melhor. Exige decidir quando dois objetos de uma lista são o mesmo objeto, o que depende de uma chave que você não especificou e que a ferramenta não consegue adivinhar; quando adivinha errado, dá uma resposta confiante e enganosa. A comparação por índice é bruta, mas nunca inventa uma correspondência que não existe.

Na prática, quando uma lista foi de fato reordenada, ordene os dois lados pelo identificador antes de comparar.

Null, ausência e como ler um caminho

Uma chave presente com valor null de um lado e totalmente ausente do outro é informada como adicionada ou removida, e não como alteração, porque são estados diferentes e o código os trata de forma diferente. Ausente geralmente significa que o campo nunca foi definido; null geralmente significa que foi definido como vazio de propósito. APIs que usam um no sentido do outro causam bugs, e esta é a ferramenta que mostra qual dos dois você está enviando de fato.

Caminhos são lidos como uma rota a partir da raiz: user.roles[2] é o terceiro elemento do array roles dentro do objeto user. Essa sintaxe é, de propósito, a mesma que você digitaria em JavaScript para chegar ao valor, e é próxima o bastante de uma expressão JSONPath para ser colada na maioria das ferramentas que aceitam uma.

Um fluxo de regressão que encontra mudanças reais

O uso mais comum de um diff de JSON é confirmar que um deploy não mudou nada que não devia. Capture a resposta da versão atual, capture de novo a da versão candidata e compare. A dificuldade nunca está na comparação em si, e sim no ruído, porque uma resposta quase sempre traz campos que mudam legitimamente a cada chamada.

Remova ou normalize esses campos antes de comparar: identificadores gerados, timestamps, request ids, URLs assinadas com prazo de validade e qualquer coisa que informe duração. Um script curto que percorre o documento e troca esses campos por um valor fixo transforma um diff de duzentas diferenças em um diff da única diferença que importa.

Faça o mesmo com a ordenação onde a API não a garante. Se uma lista de tags volta em ordem diferente a cada chamada, ordene os dois lados por uma chave estável antes de comparar; caso contrário, uma comparação por índice acusa todos os elementos como alterados e esconde entre eles a única diferença de verdade.

Igual, idêntico e equivalente

Dois documentos podem ser iguais em vários sentidos diferentes, e uma ferramenta precisa escolher um deles. A igualdade byte a byte é a mais rígida e a menos útil, já que uma simples reformatação a quebra. A igualdade estrutural - as mesmas chaves com os mesmos valores, independentemente de espaços em branco ou da ordem das chaves - é a que esta comparação realiza. A equivalência semântica é uma terceira coisa, e depende do schema.

Os números são onde a comparação estrutural fica desconfortável. O JSON não distingue inteiros de números de ponto flutuante, então 1, 1.0 e 1e0 são o mesmo valor para um parser, e a escolha de qual deles o produtor emite pode mudar entre versões de uma biblioteca sem que nada relevante tenha mudado. Zeros à direita desaparecem em uma ida e volta pelo mesmo motivo.

Strings podem diferir em bytes e ainda assim parecer idênticas, porque o Unicode permite mais de uma codificação para o mesmo caractere: uma letra acentuada pode ser um único code point ou uma letra base somada a um sinal combinante. Normalizar para NFC antes de comparar elimina essa classe de falsa diferença, e vale a pena sempre que o texto chega de mais de uma origem.

Quando o diff é o instrumento errado

Comparar dois exemplos diz algo sobre esses dois exemplos. Verificar se as respostas obedecem a uma regra é outro trabalho, e a ferramenta para isso é um schema: JSON Schema para um documento, OpenAPI para um endpoint. Um schema declara que id é inteiro e que email casa com um padrão, e isso vale para toda resposta, não apenas para o par que você capturou por acaso.

Na prática, os dois se complementam. A validação de schema na integração contínua pega desvios estruturais - um campo que mudou de tipo, uma propriedade obrigatória que sumiu - enquanto o diff responde à pergunta mais restrita de o que mudou entre esses dois payloads específicos durante uma investigação.

O teste de contrato vai além: faz o consumidor publicar aquilo de que depende, de modo que o pipeline do produtor falhe quando uma mudança quebraria um cliente real, e não apenas quando ela difere de uma amostra antiga.

Ler um caminho e ir direto até ele

Os caminhos mostrados aqui se leem como uma rota a partir da raiz: user.roles[2] é o terceiro elemento do array roles dentro do objeto user. Essa é, de propósito, a expressão que você digitaria em JavaScript para chegar ao valor, então o caminho pode ser colado num console com o documento guardado em uma variável.

O mesmo formato é próximo do JSONPath, a linguagem de consulta que a maioria das ferramentas de log e de API aceita, em que a expressão começa com um cifrão: $.user.roles[2]. O jq, processador de linha de comando, usa uma notação parecida sem o cifrão, então .user.roles[2] extrai o mesmo valor de um arquivo ou de um stream.

Em documentos muito grandes, esse caminho é a saída útil, mais do que o próprio diff. Uma vez que você sabe onde mora a diferença, o jq ou um filtro no seu cliente HTTP consegue puxar só aquela subárvore dos dois lados, o que é mais rápido de ler do que qualquer visão do documento inteiro.

Comparar documentos grandes demais para ler

Uma resposta de vários megabytes é comum em exportações analíticas e endpoints de carga em lote, e compará-la por inteiro produz um resultado que ninguém consegue usar. A abordagem produtiva é reduzir antes de comparar: selecione a subárvore que interessa, projete apenas os campos em discussão e ordene as coleções pelo identificador.

Contar é uma boa primeira passada. Se um lado tem 4.812 registros e o outro tem 4.811, a pergunta interessante é qual registro está faltando, e isso é uma comparação de conjuntos de identificadores, não um diff de documentos - duas listas ordenadas de ids e um diff por linhas resolvem em segundos.

Quando os payloads são realmente grandes e a comparação precisa rodar repetidas vezes, um pipeline de linha de comando ganha do navegador: jq para normalizar os dois arquivos em linhas compactas e ordenadas, e depois diff. Em qualquer dos casos tudo roda localmente, mas a linha de comando não tem uma página para manter responsiva.

Perguntas frequentes

A ordem das chaves afeta a comparação?
Não. Objetos são comparados por chave, não por posição, então reordenar chaves não gera diferença alguma. Elementos de array são comparados por índice, porque em arrays a ordem tem significado.
Meus dados são enviados para um servidor?
Não. A comparação roda inteiramente no seu navegador usando JavaScript. Nenhum dos dois documentos sai do seu dispositivo, o que torna isso seguro para respostas de API de produção.
O que significa um caminho como user.roles[2]?
Ele localiza a diferença dentro do documento: o terceiro elemento do array roles, dentro do objeto user. Pontos separam chaves de objeto e colchetes indexam arrays.
Qual a diferença entre um valor null e uma chave ausente?
Elas são relatadas de formas diferentes. Uma chave presente com valor null de um lado e ausente do outro aparece como adicionada ou removida, não como alterada.

Última atualização:

Relatar um problema

Inclua os passos, o resultado esperado e o resultado real. Remova segredos e dados pessoais.

Ferramentas relacionadas