Validação com JSON Schema: como validar JSON em Node, Python e no navegador (2026)
Em resumo: JSON Schema é um contrato para dados JSON. Você declara tipos de campos, chaves obrigatórias e restrições, e um validador confere se um documento JSON segue esse contrato. Use Ajv no Node quando quiser a validação mais rápida, a biblioteca jsonschema em Python quando precisar de schemas portáveis e empacote o Ajv no navegador para dar feedback instantâneo em formulários e configurações. Para novos projetos em 2026, vá direto no Draft 2020-12.
Este texto cobre o menor exemplo funcional, padrões completos nos três runtimes e os bugs clássicos do tipo “a validação passa, mas a produção rejeita os dados”.
O que é (e o que não é) JSON Schema
Uma definição em uma frase
Um JSON Schema é um documento JSON que descreve a forma de outros documentos JSON. O validador lê o schema mais os dados e devolve “passou” ou a lista de caminhos que falharam.
O menor exemplo útil:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"]
}
{"name": "Alice"} passa. {"age": 30} falha (sem name). {"name": 42} também falha, porque name não é string. Esse é todo o modelo mental.
JSON Schema vs validação de sintaxe JSON
São dois problemas distintos que costumam ser confundidos.
| Dimensão | Verificação de sintaxe JSON | Validação com JSON Schema |
|---|---|---|
| O que verifica | É um documento JSON válido? | O JSON corresponde ao contrato? |
| Detecta | Vírgulas faltando, aspas simples, comentários | Tipos errados, campos obrigatórios ausentes, valores fora do intervalo |
| Ferramentas | JSON.parse(), Formatador JSON | Ajv, jsonschema (Python), fastjsonschema |
| Quando recorrer | Primeiro passo, antes do parsing | Logo após o parsing, antes da lógica de negócio |
Na prática, você faz as duas coisas. Primeiro formata o payload no Formatador JSON pra ver se ele faz parse, e só depois manda pro validador conferir se segue o contrato.
JSON Schema vs JSONPath, JSON Patch, jq e TypeScript
Cinco ferramentas dividem esse espaço de problema. A matriz de decisão:
| Ferramenta | Pergunta que responde | Quando recorrer |
|---|---|---|
| JSON Schema | Esse JSON corresponde à estrutura esperada? | Validar entrada de API, arquivos de configuração, payloads de formulário |
| JSONPath | Como consulto um valor dentro desse JSON? | Extrair campos aninhados, leituras em lote |
| JSON Patch (RFC 6902) | Como descrevo o diff de A para B? | Edição colaborativa, sincronização incremental |
| jq | Como processo JSON na linha de comando? | Scripts de shell, pipelines de log, verificações de CI |
| Tipos do TypeScript | Meu código usa essa forma corretamente? | Garantias em tempo de compilação dentro de um único codebase |
A linha divisória é simples: JSON Schema valida dados desconhecidos em tempo de execução, TypeScript valida código conhecido em tempo de compilação. O TypeScript não te ajuda com JSON que veio de um webhook de terceiros nem de um colar do usuário, e é justamente aí que entra o JSON Schema. Zod e Pydantic ficam no meio do caminho (tipos em compilação mais validação em runtime), e a gente volta neles mais adiante.
JSON Schema vs OpenAPI
Um equívoco comum é achar que OpenAPI substitui o JSON Schema. Não substitui. O OpenAPI usa o JSON Schema internamente para descrever corpos de requisição e resposta, e por cima disso adiciona caminhos, parâmetros, esquemas de segurança e URLs de servidor. O schema é o contrato da forma dos dados; o OpenAPI é o contrato da API que envolve esse schema.
| Dimensão | JSON Schema | OpenAPI |
|---|---|---|
| Escopo | Forma de um documento JSON | Forma de uma API HTTP inteira |
| Dependências | Nenhuma (o schema é JSON autocontido) | Importa JSON Schema para definir corpos |
| Pareamento de versões | Draft 7 / Draft 2019-09 / Draft 2020-12 | OpenAPI 3.0 usa um subconjunto do Draft 4; OpenAPI 3.1 usa Draft 2020-12 nativamente |
| Uso típico | Arquivos de configuração, envelopes de mensagem, validação de formulário, contratos de payload único | Design de API REST, geração de SDK, mock servers, testes de contrato |
| Geração de código | Limitada (algumas ferramentas no estilo quicktype) | Ecossistema maduro (openapi-generator, oapi-codegen, SDKs de fornecedores) |
| Gestão de contratos | Um arquivo por forma, sem roteamento | Caminhos, operações, fluxos de autenticação e endpoints versionados em um único documento |
Vá de JSON Schema puro quando o artefato que importa é um documento único: um payload de webhook, um arquivo de configuração, uma mensagem de fila ou um formulário. Não tem superfície HTTP pra descrever, então OpenAPI é peso desnecessário.
Vá de OpenAPI quando você está publicando uma API HTTP e quer um documento só pra dirigir a documentação, a geração de SDK, mock servers e testes de contrato. Defina seus schemas primeiro como arquivos JSON Schema autônomos em um diretório schemas/, e depois faça $ref neles a partir do documento OpenAPI. Assim os schemas continuam reutilizáveis fora do contexto da API.
O pareamento de versões costuma confundir os times. O OpenAPI 3.0 usa um subconjunto do Draft 4, então você não pode usar palavras-chave do Draft 2020-12 como prefixItems ou unevaluatedProperties dentro de um documento 3.0 — os geradores ignoram essas chaves silenciosamente. O OpenAPI 3.1 é um superconjunto do Draft 2020-12, então tudo que é válido em 2020-12 vale em 3.1. Se você puder escolher, mire em OpenAPI 3.1 e escreva schemas Draft 2020-12 em todo lugar.
Seu primeiro JSON Schema (5 minutos)
As palavras-chave que você precisa primeiro
Com essas aqui você já cobre uns 80% dos casos:
{
"type": "object",
"properties": {
"id": { "type": "integer", "minimum": 1 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 },
"tags": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
"role": { "enum": ["admin", "editor", "viewer"] },
"metadata": { "type": "object", "additionalProperties": true }
},
"required": ["id", "email"],
"additionalProperties": false
}
As peças do vocabulário:
type:string,number,integer,boolean,null,array,objectproperties+required: declara campos e marca quais precisam aparecerenum/const: restringe a um conjunto fixo ou a um único literalminimum/maximum/multipleOf: limites numéricosminLength/maxLength/pattern: tamanho de string e regexminItems/maxItems/uniqueItems: formato de arrayadditionalProperties: false: rejeita chaves não declaradas. Sempre defina isso em contratos de entrada.
Exemplos de JSON Schema por caso de uso
As palavras-chave acima aparecem em combinações diferentes dependendo do que você está validando. Algumas formas representativas:
Corpo de requisição de API — um endpoint de cadastro que aceita e-mail e senha:
{
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"password": { "type": "string", "minLength": 8, "maxLength": 128 }
},
"required": ["email", "password"],
"additionalProperties": false
}
Arquivo de configuração — uma config de logger que trava o nível em um conjunto fixo:
{
"type": "object",
"properties": {
"level": { "enum": ["debug", "info", "warn", "error"] },
"output": { "type": "string", "default": "stdout" }
},
"required": ["level"],
"additionalProperties": false
}
Payload de formulário com regras condicionais — quando accountType é "business", taxId passa a ser obrigatório:
{
"type": "object",
"properties": {
"accountType": { "enum": ["personal", "business"] },
"taxId": { "type": "string" }
},
"if": { "properties": { "accountType": { "const": "business" } } },
"then": { "required": ["taxId"] }
}
Registro JSON de linha de CSV — uma linha de uma tabela de pedidos exportada:
{
"type": "object",
"properties": {
"orderId": { "type": "string", "pattern": "^ORD-[0-9]{6}$" },
"orderedOn": { "type": "string", "format": "date" },
"totalUsd": { "type": "number", "minimum": 0 }
},
"required": ["orderId", "orderedOn", "totalUsd"]
}
Envelope de evento de webhook — oneOf discrimina pelo literal type, então cada variante de evento tem seu próprio formato de payload:
{
"oneOf": [
{ "properties": { "type": { "const": "order.created" }, "data": { "$ref": "#/$defs/order" } } },
{ "properties": { "type": { "const": "order.refunded" }, "data": { "$ref": "#/$defs/refund" } } }
]
}
Esses cinco exemplos cobrem o grosso do que os times escrevem na prática. Copie o que mais se aproxima e ajuste os nomes dos campos — o vocabulário das palavras-chave continua o mesmo.
Validar sem instalar nada
Cole schema e payload no playground em ajv.js.org ou em jsonschemavalidator.net e você tem o veredito na hora. Se o próprio JSON estiver com cara estranha, passe antes pelo Formatador JSON.
Validando em Node.js com Ajv
Instalação e exemplo em 12 linhas
O Ajv compila seu schema em uma função otimizada na primeira chamada de compile e a reutiliza depois.
npm install ajv
import Ajv from "ajv";
const ajv = new Ajv();
const schema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "integer", minimum: 0 }
},
required: ["name"]
};
const validate = ajv.compile(schema);
const data = { name: "Alice", age: 30 };
if (!validate(data)) console.log(validate.errors);
else console.log("OK");
Mudando para o Draft 2020-12
O construtor Ajv padrão continua fixado no Draft 7 por compatibilidade com versões antigas. Pra usar o 2020-12 você precisa pedir explicitamente:
import Ajv2020 from "ajv/dist/2020";
const ajv = new Ajv2020({ strict: true, allErrors: true });
Agora prefixItems, unevaluatedProperties e $dynamicRef ficam disponíveis. A seção sobre Draft 2020-12 mais abaixo explica o que cada um deles faz.
Ativando a validação de format
Esta aqui pega mais gente do que qualquer outra peculiaridade do Ajv: format: "email" não faz nada por padrão. A própria spec trata format como informativo, então você precisa registrar o módulo de formatos:
npm install ajv-formats
import addFormats from "ajv-formats";
addFormats(ajv); // agora "format": "email" valida de fato
Sem esse passo, {"email": "not-an-email"} passa tranquilo em um schema que exige format: "email". Em produção, sempre instale o ajv-formats.
Middleware Express na vida real
Um validador por rota, compilado quando o servidor sobe:
import express from "express";
import Ajv2020 from "ajv/dist/2020";
import addFormats from "ajv-formats";
const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);
const validateUser = ajv.compile({
type: "object",
properties: {
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 13 }
},
required: ["email"],
additionalProperties: false
});
const app = express();
app.use(express.json());
app.post("/users", (req, res) => {
if (!validateUser(req.body)) {
return res.status(400).json({ errors: validateUser.errors });
}
// ... lógica de negócio
res.status(201).json({ ok: true });
});
O erro mais caro que dá pra cometer aqui é chamar ajv.compile(schema) lá dentro do handler. Compile uma vez no escopo do módulo e reaproveite a função retornada em cada requisição. Recompilar a cada chamada derruba o throughput em 50 vezes ou mais.
Validando em Python com jsonschema
Instalação e uso básico
pip install jsonschema
from jsonschema import validate, ValidationError
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name"]
}
try:
validate(instance={"name": "Alice", "age": 30}, schema=schema)
print("OK")
except ValidationError as e:
print("FAIL:", e.message, "at", list(e.absolute_path))
Coletando todos os erros com Draft202012Validator
A função validate() lança exceção logo no primeiro erro. Quando você quer listar tudo de uma vez, o que é bem prático em resposta de formulário, vá de iter_errors:
from jsonschema import Draft202012Validator
validator = Draft202012Validator(schema)
errors = sorted(validator.iter_errors(instance), key=lambda e: e.path)
for err in errors:
print(f" - {'/'.join(map(str, err.absolute_path))}: {err.message}")
Aí o usuário corrige tudo de uma vez, sem aquele ping-pong entre cliente e servidor.
jsonschema vs Pydantic: quando escolher cada um
Duas bibliotecas Python fortes que resolvem problemas diferentes.
| Dimensão | jsonschema | Pydantic v2 |
|---|---|---|
| Formato do schema | Um dict JSON (o schema é dado) | Uma classe Python com type hints |
| Performance | Interpretado, 10 a 100 vezes mais lento que Pydantic | Núcleo em Rust, o mais rápido do ecossistema |
| Portabilidade entre linguagens | Sim (o mesmo schema roda em JS, Go, Rust) | Não (só Python) |
| Integração com FastAPI / modelos nativos | Conversão manual | Integrada |
Palavras-chave completas do Draft 2020-12 ($dynamicRef, etc.) | Completa | Parcial |
A regra que se sustenta em produção é direta: use jsonschema para contratos entre linguagens (OpenAPI, APIs públicas, webhooks) e Pydantic para serviços internos em Python. Muito time roda os dois lado a lado, com jsonschema no gateway impondo o contrato e Pydantic na camada da aplicação para a lógica de negócio tipada. O schema é o artefato portátil, idêntico ao que você daria pro Ajv.
Validando no navegador
Por que validar do lado do cliente, afinal
Três motivos, em ordem de importância. O primeiro é UX: feedback instantâneo enquanto o usuário digita ganha de qualquer ida e volta ao servidor. O segundo é banda, porque erros óbvios nunca saem do navegador. O terceiro é higiene de segurança, já que diminui o volume de lixo que chega ao backend, sem nunca substituir a validação no servidor.
E aqui vai o aviso de praxe: nunca confie só na validação do lado do cliente. Valide de novo no servidor.
Empacotando o Ajv para o navegador
npm install ajv ajv-formats
import Ajv2020 from "ajv/dist/2020";
import addFormats from "ajv-formats";
const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);
export const validateForm = ajv.compile({
type: "object",
properties: {
email: { type: "string", format: "email" },
password: { type: "string", minLength: 8 }
},
required: ["email", "password"]
});
O bundle pesa uns 30 KB gzipped, o que é considerável mas não chega a ser catastrófico. Times escolhem o Ajv quando querem uma única definição de schema compartilhada entre servidor e cliente.
Alternativas mais leves: Zod e Valibot
Se você não depende do ecossistema JSON Schema e já tá em TypeScript, um validador nativo em TS dá bundle menor e inferência de tipo mais apertada:
import { z } from "zod";
const UserSchema = z.object({
email: z.string().email(),
password: z.string().min(8)
});
const result = UserSchema.safeParse(data);
if (!result.success) console.log(result.error.issues);
O Valibot pesa uns 3 KB gzipped e tem uma API parecida. Escolha ele quando o tamanho do bundle for o que mais importa. Tem um porém: nenhuma das duas bibliotecas produz JSON Schema. Se você precisa de uma fonte única de verdade compartilhada com backends, clientes de terceiros ou geradores de OpenAPI, fique com o Ajv. Se tudo é TypeScript seu, Zod e Valibot são mais ergonômicos.
O que o Draft 2020-12 acrescenta
prefixItems para validação de tuplas
O Draft 7 expressava tuplas usando items: [] junto com additionalItems. O Draft 2020-12 separa isso de jeito limpo:
{
"type": "array",
"prefixItems": [
{ "type": "string" },
{ "type": "number" }
],
"items": false
}
["x", 42] passa. ["x", 42, "extra"] falha. O schema lê exatamente como se comporta.
unevaluatedProperties para schemas compostos
Tem um bug sutil que cedo ou tarde morde todo time que usa allOf ou oneOf. O additionalProperties: false só checa o nível imediato em que aparece, e subschemas irmãos dentro de allOf ficam livres pra declarar qualquer propriedade. O fix do 2020-12 é unevaluatedProperties: false:
{
"allOf": [
{ "$ref": "#/$defs/base" }
],
"unevaluatedProperties": false
}
Isso rejeita qualquer propriedade que nenhum ramo tenha avaliado, que é o comportamento que a maioria dos devs esperava de additionalProperties: false.
$dynamicRef para schemas recursivos
Quem já tentou declarar um schema de árvore recursiva no Draft 7 sabe o tamanho da contorção. $dynamicRef junto com $dynamicAnchor resolvem o problema:
{
"$dynamicAnchor": "node",
"type": "object",
"properties": {
"value": { "type": "string" },
"children": { "type": "array", "items": { "$dynamicRef": "#node" } }
}
}
A recursão fica declarativa e pode ser sobrescrita por descendentes sem precisar reescrever $id.
Draft 7 vs 2020-12: qual escolher
- Projeto novo, toolchain moderna: vá de Draft 2020-12.
- Está construindo ou consumindo OpenAPI 3.1: o 2020-12 é o dialeto nativo.
- Mexe com OpenAPI 3.0 ou serviços mais antigos: vá de Draft 4. O OpenAPI 3.0 usa um subconjunto do Draft 4, e misturar dialetos só dá dor de cabeça.
- Precisa de ampla compatibilidade entre validadores (Postman, ferramentas antigas de CI): o Draft 7 ainda é o formato de intercâmbio mais seguro.
Todo validador moderno já suporta o 2020-12, incluindo Ajv, jsonschema em Python, jsonschema-rs e networknt/json-schema-validator em Java.
Padrões do mundo real
Validação de entrada de API
O middleware Express ali em cima é o formato pronto pra produção. Duas práticas que vale colar nele: mantenha todos os schemas em um diretório schemas/ na raiz do repositório e adicione um passo de CI que rode ajv test (ou o equivalente em Python) pra validar os próprios schemas contra o meta-schema do JSON Schema.
Arquivos de configuração
O Visual Studio Code já vem integrado com a SchemaStore, com autocomplete e validação inline para package.json, tsconfig.json e dezenas de outros arquivos. Adicione um campo $schema nas suas próprias configs e os usuários do editor passam a ter o mesmo tratamento.
Fixtures de teste em CI
Fixtures de teste apodrecem. Alguém atualiza um modelo, uma fixture fica no formato antigo, o teste continua passando porque as asserções nunca encostaram no campo alterado. Pegue isso com uma checagem de schema antes das asserções rodarem:
import { glob } from "glob";
const files = await glob("__tests__/fixtures/*.json");
for (const f of files) {
const data = JSON.parse(await fs.readFile(f, "utf8"));
if (!validate(data)) throw new Error(`${f}: ${ajv.errorsText(validate.errors)}`);
}
Quando a checagem de schema dispara, o passo natural seguinte é um diff estrutural. Leve a fixture pro Comparar JSON e compare contra uma amostra recente de produção pra ver o que mudou. Se timestamps e IDs dominarem o diff, aplique os padrões de path-ignore para snapshots do guia de JSON diff pra separar sinal de ruído.
Payloads de webhook (Stripe, GitHub)
Webhooks de terceiros estão entre os lugares onde JSON Schema mais paga seu custo. O webhook é um contrato, o provedor pode mudar esse contrato, e você quer saber no instante em que isso acontece. Stripe e GitHub publicam descrições OpenAPI das quais dá pra extrair JSON Schemas. Valide os eventos que chegam e qualquer atualização incompatível acende o seu monitoramento, em vez de corromper estado silenciosamente.
Validação de formulários orientada a schema
O React Hook Form tem um adapter @hookform/resolvers/ajv, e o VeeValidate do Vue tem um plugin Ajv equivalente. Os dois conduzem a renderização do formulário, as mensagens de erro e a validação no envio a partir de um único JSON Schema. O schema vira a fonte única de verdade e a UI só herda as regras dele.
Mensagens de erro amigáveis
Por que os defaults são ásperos
De fábrica, o Ajv devolve erros do tipo #/properties/email format must match "email". Tudo bem pra um dev debugando um 400. Não serve pra um usuário tentando preencher um formulário de checkout.
ajv-errors para mensagens customizadas
npm install ajv-errors
import ajvErrors from "ajv-errors";
ajvErrors(ajv);
const schema = {
type: "object",
properties: { email: { type: "string", format: "email" } },
required: ["email"],
errorMessage: {
properties: { email: "Por favor, informe um endereço de e-mail válido" },
required: { email: "O e-mail é obrigatório" }
}
};
A palavra-chave errorMessage fica dentro do próprio schema, então as regras de validação e o texto que o usuário vê viajam juntos.
ajv-i18n para erros traduzidos
O ajv-i18n traz traduções das mensagens padrão em mais de 30 idiomas. Uma linha na inicialização e o seu validador passa a falar espanhol, francês, japonês ou qualquer locale que você atenda. Vale como rede de proteção quando seus overrides em errorMessage não cobrem toda restrição.
Mapeando paths do schema para campos do formulário
Cada erro do Ajv vem com um instancePath no formato /users/0/email. A maioria das libs de formulário espera paths em notação de ponto, tipo users[0].email. Um one-liner resolve:
const fieldPath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
No jsonschema do Python, o equivalente está em error.absolute_path. Junte com . pra ter o mesmo efeito.
Cinco armadilhas que passam na validação e quebram em produção
1. format é informativo por padrão
Sem o ajv-formats mais o addFormats(ajv), toda palavra-chave format é um no-op. {"format": "email"} aceita "not-an-email" numa boa. Em produção, sempre instale o pacote de formatos.
2. additionalProperties tem default true
Sem additionalProperties: false, o seu schema aceita qualquer campo não declarado. Clientes podem mandar campos extras que escapam totalmente da validação. Deixe additionalProperties: false como padrão em contratos de entrada e só relaxe quando tiver motivo claro pra isso.
3. additionalProperties não compõe
Dentro de allOf, oneOf ou anyOf, additionalProperties: false só inspeciona propriedades no próprio nível. Subschemas irmãos passam batido. O fix do Draft 2020-12 é unevaluatedProperties: false.
4. $ref remoto é risco em produção
$ref: "https://example.com/schema.json" faz o Ajv buscar pela rede na primeira compilação. Isso significa latência, exposição a DoS se o host remoto travar e uma superfície de ataque MITM. Inline todos os destinos de $ref, ou carregue eles do disco na hora do build.
5. Schemas gerados se afastam dos dados reais
Ferramentas como quicktype e typescript-json-schema geram schemas a partir de tipos que já existem. A saída costuma ser permissiva demais: todo campo opcional, additionalProperties aberto. Trate os schemas gerados como rascunho, aperte na mão e rode CI que valide amostras reais de produção contra o schema (e o contrário também), pra que o desvio apareça cedo.
Performance: números e regras de bolso
- Ajv no Node.js: validadores compilados resolvem uma checagem em bem menos de um microssegundo. É o validador JS de produção mais rápido que existe hoje.
jsonschemaem Python: interpretado, 10 a 100 vezes mais lento que o Pydantic. Quando isso pesar, troque porfastjsonschema, que gera código Python e chega perto do Ajv.- Rust e Go:
jsonschema-rsexeipuuv/gojsonschemate dão de 2 a 5 vezes mais throughput em cima do Ajv na camada de gateway. - O maior ganho isolado é pré-compilar. Chame
ajv.compile(schema)uma vez no carregamento do módulo e reaproveite o validador retornado em toda requisição. Recompilar a cada requisição mata o throughput em 50 vezes ou mais.
Perguntas frequentes
O que é validação com JSON Schema, em palavras simples?
A validação com JSON Schema confere se um documento JSON segue um contrato. Esse contrato (o schema) também é JSON e declara tipos, campos obrigatórios e restrições. O validador lê schema mais dados e devolve “passou” ou os caminhos que falharam, junto com o motivo de cada falha.
Como valido JSON contra um schema online?
Cole schema e dados no playground em ajv.js.org ou em jsonschemavalidator.net e você tem o veredito na hora. Se o JSON estiver mal formado, ajeite antes no Formatador JSON. Os dois rodam no navegador, sem upload.
Qual é o validador de JSON Schema mais rápido em 2026?
No Node, o Ajv com validadores pré-compilados resolve uma checagem em menos de um microssegundo. Em Python, o fastjsonschema gera código e atinge throughput da mesma classe do Ajv. Na camada de gateway, jsonschema-rs (Rust) e gojsonschema (Go) ficam 2 a 5 vezes mais rápidos que o Ajv. Independentemente da escolha, pré-compile uma vez e reaproveite.
Qual é a diferença entre JSON Schema e tipos do TypeScript?
O TypeScript checa o código que você escreve em tempo de compilação. O JSON Schema checa JSON desconhecido em tempo de execução. O TypeScript não enxerga o JSON que chega de uma resposta HTTP, de um arquivo ou de um colar do usuário, e é exatamente aí que entra o JSON Schema.
Devo usar Draft 2020-12 ou Draft 7?
Para projetos novos em 2026, vá de Draft 2020-12. prefixItems, unevaluatedProperties e $dynamicRef resolvem problemas que doem na prática. O OpenAPI 3.1 usa o 2020-12 nativamente. Fique no Draft 7 só por compatibilidade com Postman ou serviços mais antigos. Já o OpenAPI 3.0 usa um subconjunto do Draft 4, e misturar dialetos não termina bem.
Como gero um JSON Schema a partir de um JSON existente?
Você tem três opções. Pode colar amostras em quicktype.io ou jsonschema.net, pode rodar npx genson-js ou pip install genson && genson sample.json na linha de comando, ou pode escrever à mão. Schemas gerados automaticamente costumam ser permissivos demais (todo campo opcional, additionalProperties: true), então sempre aperte na mão antes de tratar como contrato.
O JSON Schema pode substituir o OpenAPI?
Não. O OpenAPI usa JSON Schema internamente pra descrever corpos de requisição e resposta, e em cima disso adiciona paths, esquemas de segurança, parâmetros e URLs de servidor. Os dois se complementam: escreva seus schemas, referencie eles num documento OpenAPI e você tem contratos de API completos.
JSON Schema é a mesma coisa que JSONPath ou jq?
Resolvem problemas diferentes. JSON Schema valida estrutura (“esse JSON segue o contrato?”). JSONPath e jq extraem valores (“todo nome de pod na fase Running”). Valide com um schema, consulte com JSONPath ou jq.
Por que minha validação do Ajv passa mas a produção rejeita os dados?
Três culpados cobrem quase todos os casos. Primeiro, esqueceu o ajv-formats, então o format: "email" nunca validou de verdade. Segundo, omitiu o additionalProperties: false e deixou passar campos extras do cliente. Terceiro, usou additionalProperties: false dentro de allOf ou oneOf e descobriu que ele não compõe. Nesse último caso, troque por unevaluatedProperties: false.
Posso customizar as mensagens de erro do JSON Schema para usuários finais?
Pode. No Node, instale o ajv-errors pra embutir errorMessage dentro do schema, e o ajv-i18n pra ter traduções em mais de 30 locales. Em Python, o jsonschema expõe o contexto completo de validação em cada objeto de erro, então você consegue mapear tipo de erro mais path pro texto que o seu design system usar.