Ir para o conteúdo
jsonbeautifiers
Português

Comparar JSON

Compara dois documentos pela estrutura: reformatar ou mudar a ordem das chaves não conta.

Original
Alterado

Nada do que você cola sai do seu navegador. A lista de permissões connect-src transforma isso em uma garantia do navegador, e não em uma promessa. Confira você mesmo

Isto compara dois documentos pela estrutura, não pela linha. Reformate um deles, ordene as chaves, mude a indentação: o diff continua vazio, porque nada disso alterou os dados.

Um diff de texto sobre JSON é praticamente inútil exatamente por isso. Reserialize um documento com outra biblioteca e todas as linhas aparecem como alteradas sem que nada tenha mudado.

O que conta como diferença

Quatro tipos de mudança, cada um reportado com um JSON Pointer para o local exato.

Adicionado
Uma chave ou elemento presente à direita e ausente à esquerda.
Removido
Presente à esquerda e ausente à direita.
Alterado
Presente nos dois com um valor diferente do mesmo tipo.
Tipo trocado
Presente nos dois com um tipo diferente. Vale separar, porque um número que virou string costuma ser um bug de serialização e não uma mudança de dados.

Como os arrays são comparados, e por que isso importa

Arrays são a parte difícil de qualquer diff estrutural, e não existe uma única resposta certa.

Comparar posição a posição está certo para uma tupla de formato fixo e é catastrófico para uma lista. Insira um elemento no começo de um array de mil elementos e todas as posições seguintes aparecem como alteradas, o que enterra a única diferença real embaixo de mil falsas.

Esta ferramenta faz em vez disso uma passada de maior subsequência comum, então uma inserção é reportada como inserção. Isso custa tempo proporcional ao produto dos dois comprimentos, então acima de um limiar ela volta para a comparação posicional e diz isso nas notas. Ela nunca degrada em silêncio.

Coisas que de propósito não são diferenças

A ordem das chaves
Objetos JSON não têm ordem segundo a especificação. Dois documentos com os mesmos membros em ordem diferente são o mesmo documento.
Espaço em branco e indentação
Não significativos para um parser, então invisíveis aqui.
0 e -0
Distintos sob Object.is, mas idênticos como números JSON.

How to do this in code

Comparação estrutural em código.

sh jq

A primeira linha é a que vale lembrar. Ordenar as chaves antes tira quase todo o ruído de um diff de texto.

# Sort keys recursively, then diff the text. Cheap and
# surprisingly effective for small documents.
diff <(jq -S . a.json) <(jq -S . b.json)

# Keys present in one and not the other
jq -n --slurpfile a a.json --slurpfile b b.json \
  '($a[0] | paths) - ($b[0] | paths)'
py Python
from deepdiff import DeepDiff

diff = DeepDiff(a, b, ignore_order=True)
print(diff)

# Standard library only, for a shallow comparison
changed = {k for k in a.keys() | b.keys() if a.get(k) != b.get(k)}
js JavaScript

JSON Patch é o formato padrão para transmitir um diff de JSON, e os caminhos dele são ponteiros RFC 6901 como os que esta ferramenta reporta.

import { compare } from 'fast-json-patch';

// RFC 6902 JSON Patch: a list of operations that turns a into b
const patch = compare(a, b);
// [{ op: 'replace', path: '/user/name', value: 'Priya' }]
go Go

go-cmp é a escolha padrão nos testes em Go e produz uma saída legível para estruturas aninhadas.

import "github.com/google/go-cmp/cmp"

if d := cmp.Diff(a, b); d != "" {
    t.Errorf("mismatch (-want +got):\n%s", d)
}

Perguntas frequentes

Por que reordenar as chaves não mostra diferença nenhuma?
Porque a RFC 8259 define um objeto como uma coleção sem ordem. Na prática todo parser preserva a ordem de inserção, e diffs que dependem disso estão testando a serialização e não os dados.
Dá para ignorar campos como timestamps?
Sim. Ignorar um caminho tira ele por completo da comparação, que é como se comparam duas respostas de API que só diferem em requestId e generatedAt.
O que é um JSON Pointer?
A RFC 6901: um caminho separado por barras como /users/0/email. Dentro de uma chave dois caracteres precisam de escape: ~ vira ~0 e / vira ~1. Cada mudança reportada aqui carrega um, então você consegue endereçar o local por programa.