JSON Toolkit

FAQ de consulta e validação: JSONPath e Schema

Resumo: Se consultas de caminho devem usar JSONPath ou Pointer, como escolher entre Patch e Merge Patch, o que o Schema consegue bloquear e como escolher uma versão de draft — tudo explicado em uma passada.

Intermediário~12 min de leituraPublicado em 2026-10-30Atualizado em 2026-10-30
Capa da FAQ de consulta e validação

Resumo da ópera: use JSONPath para encontrar nós por condição, JSON Pointer ou JSON Patch para endereçar e editar localizações exatas, e Merge Patch para mesclar configuração; fixe Schemas novos em 2020-12 e ative allErrors para que toda falha volte em uma passada.

Consultar é "achar"; validar é "confirmar que está certo". Cada um tem seu próprio padrão, então não os misture.

1. Consultas de caminho

Como devo escolher entre JSONPath e JSON Pointer?

Depende se você está "achando por condição" ou "buscando por posição".

Dimensão JSONPath JSON Pointer
Padrão RFC 9535 (2024) RFC 6901 (2013)
Expressão $.users[?(@.age > 18)].id /users/0/id
Wildcards e recursão Suporta *, .. Não suportado
Filtragem condicional Suportada Não suportada
Resultado Um conjunto de nós (possivelmente vários) Uma única localização
Expressividade Forte, mas com um pesado legado de diferenças entre implementações Fraca, mas totalmente determinística
Uso típico Extração de logs, depuração, exploração de dados JSON Patch, endereçamento de configuração, caminhos de erro

A regra de seleção é direta:

  • Precisa "pegar todos os nós que satisfazem uma condição" → JSONPath;
  • Precisa "fixar uma localização exata e adicionar, remover ou modificar" → JSON Pointer;
  • Use juntos: encontre o nó com JSONPath e descreva sua localização com Pointer.

Não use JSONPath para endereçamento em um contrato de API. JSONPath retorna um conjunto, e os três ramos para resultado vazio, de um elemento e de vários elementos complicam o protocolo; Pointer significa "ou ele aponta para uma localização existente ou não existe", o que convém melhor a um contrato.

Qual é a diferença entre $.. e $.*?

$.. é descida recursiva e percorre todos os níveis; $.* retorna apenas os filhos diretos do nível atual.

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → os dois objetos {id:1, child:{...}} e {id:3};
  • $..id → três valores: 1, 2, 3;
  • $.*.id → 1 e 3, excluindo o 2 aninhado.

A descida recursiva é prática mas cara: $.. percorre a árvore inteira, o que é O(n) em um documento grande, e facilmente junta campos profundos que você não pretendia. Se um nível concreto funciona, evite ...

Filtros podem comparar strings? Podem fazer correspondência com regex?

A comparação de strings funciona; regex depende da implementação.

Sintaxe padrão (RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // compara com outro nó

A comparação de strings usa aspas duplas (aspas simples também são permitidas na RFC 9535, mas algumas implementações mais antigas aceitam apenas aspas duplas). A comparação numérica segue a semântica numérica; misturar uma string com um número, como "10" > 9, é comportamento indefinido no padrão, então não conte com isso.

Para regex, a RFC 9535 define as funções match() e search(), mas o suporte a elas varia consideravelmente entre implementações. A abordagem segura: deixe o JSONPath fazer filtragem grosseira e faça a correspondência de regex no código depois de recuperar os resultados.

Devo usar JSON Patch ou Merge Patch?

Ambos descrevem "como mudar algo", mas sua expressividade e semântica são completamente diferentes.

Dimensão JSON Patch (RFC 6902) Merge Patch (RFC 7386)
Forma Um array de operações [{"op":"add",...}] Um objeto parcial
Operações suportadas add / remove / replace / move / copy / test Apenas merge (exclusão via null)
Endereça elementos de array Sim, por índice Não, o array inteiro é substituído
Atomicidade test viabiliza bloqueio otimista Nenhuma
Tamanho do documento Grande mas preciso Pequeno mas limitado em expressividade
Bom para Edições finas, atualizações parciais de API Mesclagem de configuração, envio parcial de formulário

Critérios de decisão:

  • Mudar um campo do terceiro elemento de um array → JSON Patch;
  • Mesclar várias entradas de configuração na configuração existente → Merge Patch;
  • Ter controle de concorrência de "falhar se o valor original não for X" → test do JSON Patch.

Observe a armadilha na semântica de null do Merge Patch: null significa "exclua esta chave", não "defina o valor como null". Definir um valor como null é impossível com Merge Patch.

2. Validação por Schema

Que problemas o JSON Schema resolve?

Ele transforma "como os dados deveriam parecer" de um acordo verbal em um contrato verificável por máquina. Os erros que ele consegue realmente bloquear:

  • Campos obrigatórios ausentes (required);
  • Tipo errado, por exemplo uma string onde se espera um número (type);
  • Valores fora da faixa (minimum / maximum / minLength);
  • Valores fora do conjunto permitido (enum);
  • Tipos de elementos de array inconsistentes ou comprimento de array errado (items / minItems);
  • Campos ausentes em estruturas aninhadas (validação recursiva por properties);
  • Campos extras (additionalProperties: false).

O que ele não resolve: regras de negócio ("o horário de fim deve ser posterior ao de início"), condições entre campos (algumas podem ser expressas com if/then, mas a legibilidade sofre), unicidade em banco de dados e verificações de permissão.

Então o Schema é o porteiro da "forma dos dados", não um substituto da lógica de negócio.

Devo usar draft-07 ou 2020-12?

Use 2020-12 para projetos novos; para sistemas existentes, verifique o que o validador suporta.

Versão Situação Mudanças-chave
draft-04 Ultrapassada Muitas bibliotecas antigas ainda usam por padrão
draft-07 Padrão de fato Suporte mais amplo, ecossistema mais rico
2019-09 Transitória Introduziu $defs, unevaluatedProperties
2020-12 Recomendada atualmente items dividido em prefixItems + items, suporta $dynamicRef

Duas armadilhas de migração:

  1. A sintaxe de tupla mudou: no draft-07 um items com valor de array expressava uma tupla; o 2020-12 mudou isso para prefixItems;
  2. $ref e palavras-chave irmãs: no draft-07 as palavras-chave irmãs são ignoradas quando $ref está presente; o 2020-12 permite que coexistam.

Se o validador só suporta draft-07, escrever sintaxe 2020-12 mesmo assim é ignorado silenciosamente — e o caso mais perigoso é "a validação passa" quando nada foi validado.

Como gero tipos TypeScript a partir de um Schema?

Duas rotas: derivar tipos do Schema, ou gerar o Schema a partir de tipos.

Do Schema para os tipos (o backend define o contrato, o frontend o consome):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

O segredo é o as const: sem ele, type: 'string' é inferido como string em vez do literal 'string', e a inferência desmorona.

Dos tipos para o Schema (o frontend já tem tipos e precisa de validação em tempo de execução):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

A vantagem desta rota é que "uma definição produz tanto os tipos quanto a validação em tempo de execução"; o custo é introduzir uma DSL, e o Schema gerado pode carregar detalhes de implementação (por exemplo uma estrutura $ref pouco atraente).

Como localizo o campo específico quando a validação falha?

Olhe o instancePath (2020-12) ou dataPath (draft-07) retornado pelo validador; é um JSON Pointer para a localização do erro.

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" diz diretamente: o campo price do terceiro elemento do array items tem o tipo errado.

Duas sugestões práticas:

  1. Retorne todos os erros de uma vez em vez de lançar no primeiro — o frontend corrigir tudo em uma passada é mais rápido do que tentativas de ida e volta;
  2. Transforme o Pointer em linguagem simples antes de mostrar ao usuário final; /items/2/price é útil para desenvolvedores e inútil para usuários.

allErrors: true é a chave na maioria dos validadores (o Ajv exige ativá-la explicitamente); o padrão costuma ser o modo fail-fast.

additionalProperties: false deve ser ativado?

Ative para contratos internos; seja cauteloso com APIs públicas.

Uma vez ativado, qualquer campo não declarado em properties falha na validação. O benefício é pegar erros de digitação (userNmae) e repasses inesperados; a desvantagem é que uma adição de campo upstream quebra consumidores downstream, destruindo a compatibilidade retroativa.

Meio-termo:

Cenário Recomendação
Entre serviços internos Ativar. Mudanças de campo passam por gerenciamento de versão
APIs externas públicas Desativar. Permita que o upstream adicione campos
Consumir dados de terceiros Desativar. Você não controla a outra ponta
Validação de configuração Ativar. Um erro de digitação na configuração deve falhar imediatamente

Como valido que os elementos de um array são únicos?

Duas semânticas diferentes — não as confunda:

{ "uniqueItems": true }

Isso deduplica o elemento inteiro, exigindo que o array não contenha dois elementos profundamente iguais.

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

O trecho acima só garante que os objetos como um todo não se dupliquem; {"id":"a","name":"x"} e {"id":"a","name":"y"} não contam como duplicatas.

Para deduplicar por um campo específico, o Schema não consegue expressar isso; você precisa validar na camada de negócio:

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id 重复');
}

O format realmente valida e-mails e datas?

Não necessariamente. Na especificação, format é uma "anotação" por padrão em vez de uma "asserção", então um validador pode coletá-lo sem validá-lo.

  • O Ajv exige instalar e configurar o pacote separado ajv-formats, caso contrário format: "email" é efetivamente inerte;
  • Alguns validadores tratam format como uma asserção ativada por padrão, então o comportamento é inconsistente;
  • Mesmo quando ativado, o regex de email é muito frouxo — a@b passa.

Conclusão: não trate format como validação forte. Faça validação estrita de e-mails, URLs e datas no seu código de negócio; manter format no Schema como documentação vale a pena, mas não espere que ele detenha dados sujos.

3. Referência rápida

Pergunta Conclusão
Encontrar nós por condição JSONPath
Endereçamento preciso, aplicar patches JSON Pointer
Mudar elementos de array JSON Patch, Merge Patch não consegue
Mesclar configuração Merge Patch
Versão de Schema para projetos novos 2020-12, atenção ao prefixItems
Gerar tipos TS a partir do Schema json-schema-to-ts + as const
Localizar erros de validação Leia instancePath, ative allErrors
Deduplicar por campo O Schema não consegue; use um Set na camada de negócio
format: email A maioria dos validadores não o verifica por padrão, então não conte com ele