JSON Toolkit

Validação JSON no CI: um pipeline com jsonlint, ajv e jq

Resumo: A validação de sintaxe prova que um arquivo é analisável; a validação de schema prova que os campos seguem o contrato. No CI, o que importa são códigos de saída inequívocos, localização de erros e caminhos de correção.

Intermediário~10 min de leituraPublicado em 2026-10-31Atualizado em 2026-10-31
Capa de Validação JSON no CI

Resumo da ópera: no CI, deixe o jq empty cuidar da sintaxe, o ajv-cli cuidar da estrutura (check-jsonschema para equipes multi-formato) e o Prettier cuidar do estilo; encadeie-os por camada e pare na primeira falha.

Passar no analisador do seu editor local não significa que o arquivo pertence ao CI; passar no CI também não significa que os campos correspondem ao contrato.

1. Separe a validação de sintaxe da validação de schema

A expressão "validação JSON" é usada de forma ampla demais, e é por isso que as equipes costumam escolher uma ferramenta para resolver dois problemas diferentes:

Camada Pergunta respondida Ferramentas típicas O que uma falha significa
Sintaxe Este arquivo é JSON válido? jq empty, jsonlint, python -m json.tool Colchetes, vírgulas, aspas ou codificação estão quebrados
Schema Campos, tipos e propriedades obrigatórias correspondem ao contrato? ajv-cli, check-jsonschema O arquivo é analisado, mas a estrutura de negócio está errada
Estilo Indentação e ordem das chaves são consistentes? Prettier, jq -S A formatação está inconsistente e os diffs são barulhentos

Essas camadas não substituem umas às outras. Um arquivo que passa a sintaxe ainda pode falhar no schema: {"price": "12.5"} é JSON válido, mas se o schema exige que price seja número, ele não é aceitável. Inversamente, por mais completo que seja o schema, um arquivo sem chave de fechamento nunca vai compilar contra ele.

Então o pipeline deve rodar sintaxe primeiro, estrutura depois e estilo por último. Pare assim que uma camada falhar, caso contrário um erro de análise se disfarça de erro de campo.

2. Camada de sintaxe: jq empty é a opção de dependência mínima

Se o jq já está na imagem do CI, a validação de sintaxe não custa quase nada:

jq empty data.json

O jq empty não produz saída; ele apenas analisa o arquivo. Sai com 0 em caso de sucesso e com um código não zero mais informações de linha/coluna em caso de falha. Isso o torna mais leve do que puxar uma árvore de dependências Node inteira, e um bom primeiro portão.

Ao validar em lote os arquivos JSON de um repositório, exclua node_modules, saída de build e caches de terceiros:

find . -name '*.json' \
  -not -path './node_modules/*' \
  -not -path './dist/*' \
  -print0 | xargs -0 -n1 jq empty

-print0 e xargs -0 lidam com espaços em nomes de arquivo, enquanto -n1 garante um arquivo por invocação para que a mensagem de erro identifique o culpado. Não passe centenas de arquivos para um único processo; as mensagens de erro vão se sobrepor.

O jsonlint é outra escolha comum, normalmente invocado como npx jsonlint -q data.json, onde -q imprime apenas erros. Ele relata números de linha e coluna, mas sua manutenção é menos ativa que a do jq, e versões diferentes toleram BOM e comentários de formas diferentes. Trate-o como um verificador de sintaxe mais legível, não como um validador de schema.

3. Camada de schema: os fundamentos do ajv-cli validate

O Ajv é o validador de schema mais usado no ecossistema JavaScript, e o ajv-cli o expõe como ferramenta de linha de comando:

ajv validate -s schema.json -d data.json

-s aponta para o arquivo de schema e -d para os dados a validar. Em caso de falha, o processo sai não zero, então o CI falha imediatamente. Projetos reais normalmente adicionam duas flags:

npx ajv-cli validate \
  -s schemas/order.json \
  -d 'fixtures/orders/*.json' \
  --strict=false \
  --errors=text

--strict=false ajuda com schemas antigos: o modo estrito trata palavras-chave desconhecidas e declarações required suspeitas como erros, o que muitos schemas legados reprovam. --errors=text produz saída legível por humanos, enquanto a estrutura JSON padrão é melhor para máquinas. Escolha conforme quem consome o resultado.

A versão de draft do schema também precisa bater. Versões principais do Ajv suportam drafts diferentes por padrão, e o ajv-cli pode selecionar um com --spec=draft7, --spec=draft2019 ou --spec=draft2020. Se um schema declara $schema: "https://json-schema.org/draft/2020-12/schema" mas a CLI o compila como draft-07, algumas palavras-chave são ignoradas silenciosamente. Para mais sobre autoria de schema e diferenças de versão, veja Começando com JSON Schema.

A validação de formato é outro requisito frequente. A palavra-chave format é apenas anotação por padrão; para fazer email, date-time e uri realmente imporem algo, carregue ajv-formats:

npx ajv-cli validate -c ajv-formats -s schema.json -d data.json

Essa etapa é fácil de esquecer, e o resultado é que um campo de e-mail aceita not-an-email. Antes de publicar, entregue ao pipeline um registro deliberadamente inválido; isso prova que a regra está ativa muito melhor do que ler a documentação dez vezes.

check-jsonschema: vários formatos e erros mais amigáveis

Se a equipe lida com YAML, TOML ou configuração de GitHub Actions além de JSON, o check-jsonschema é mais conveniente:

check-jsonschema --schemafile schemas/config.json config/app.json
check-jsonschema --schemafile schemas/pipeline.json .github/workflows/ci.yml

Suas vantagens práticas:

  1. Suporte nativo a YAML, o que convém a arquivos "com forma de JSON, mas escritos em YAML", como manifests do Kubernetes e configuração de CI.
  2. Os erros incluem caminho do arquivo, número da linha e caminho do campo, mantendo o custo de localização baixo.
  3. Há um hook de pre-commit disponível, então a integração é barata.
  4. Ele consegue buscar schemas comuns do SchemaStore, mas também aceita arquivos locais, evitando dependência de rede.

Em equipes multilinguagem, o Python costuma já estar presente, então instalar e atualizar o check-jsonschema é mais leve do que manter uma cadeia Node. Deixe o jq cuidar da sintaxe e esta ferramenta cuidar da estrutura; a combinação tem custo de manutenção muito baixo.

4. Códigos de saída são o único sinal confiável do CI

Não importa quão polida seja a saída, o CI só lê códigos de saída. Três armadilhas são comuns:

Sintoma Causa Correção
Há erros mas o CI está verde O comando termina com `
Não há erros mas o CI está vermelho O diff trata "diferenças encontradas" como falha Distinga "diferença" de "erro de execução"
A saída de erro está truncada A saída paralela multiprotocolo se sobrescreve Rode em série ou imprima por arquivo

Lembre-se da semântica de código de saída do jq: análise normal retorna 0; com -e, um resultado de expressão false ou null retorna 1; erros de uso e arquivos ausentes retornam 2; erros de compilação de script retornam 3. Ferramentas de schema geralmente usam 0 para sucesso, 1 para falha de validação e 2 para erros de ferramenta ou argumento. Ao escrever um wrapper, não colapse todo código não zero em "os dados estão errados", ou uma falha de ambiente será diagnosticada erroneamente como problema de conteúdo.

5. pre-commit: pegue os problemas antes do commit

O valor do pre-commit não é "mais uma verificação"; é dar feedback aos desenvolvedores em um minuto em vez de depois de uma execução de CI de dez minutos. Uma configuração mínima fica assim:

repos:
  - repo: https://github.com/python-jsonschema/check-jsonschema
    rev: 0.33.0
    hooks:
      - id: check-jsonschema
        files: ^configs/.*\.json$
        args:
          - --schemafile
          - schemas/config.json
  - repo: local
    hooks:
      - id: jq-syntax
        name: jq syntax check
        entry: jq empty
        language: system
        files: ^configs/.*\.json$

Dois detalhes importam:

  • files deve corresponder apenas aos diretórios que precisam de validação. Não rode hooks contra node_modules nem arquivos gerados.
  • O hook local pressupõe que o jq esteja instalado na máquina do desenvolvedor. Documente esse requisito, ou mude para um hook containerizado.

Se o projeto já usa Husky e lint-staged, combine-os diretamente:

{
  "lint-staged": {
    "configs/*.json": ["jq empty", "prettier --check"]
  }
}

Lembre que o lint-staged passa uma lista de arquivos preparados, então o comando precisa aceitar vários caminhos. Tanto jq empty quanto prettier --check aceitam. Não deixe um hook varrer o repositório inteiro; os commits ficarão notavelmente mais lentos.

6. GitHub Actions: um pipeline que você pode copiar

O workflow abaixo faz validação de sintaxe, validação de schema e preserva o relatório em caso de falha:

name: json-validate

on:
  push:
    paths:
      - 'schemas/**'
      - 'fixtures/**'
  pull_request:

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Syntax check
        run: |
          find fixtures -name '*.json' -print0 | xargs -0 -n1 jq empty
      - name: Schema check
        run: |
          npx --yes ajv-cli validate \
            -s schemas/order.json \
            -d 'fixtures/orders/*.json' \
            --strict=false \
            --errors=text

O filtro paths evita que mudanças apenas de documentação disparem a validação. npx --yes convém a repositórios sem dependências fixadas; projetos de produção devem adicionar ajv-cli a devDependencies e fixar a versão. Sem lock, um lançamento upstream que mude o draft padrão ou o modo estrito pode deixar o pipeline vermelho da noite para o dia.

7. Localização de erros: de números de linha a caminhos de campo

Metade do valor de um validador é a detecção; a outra metade é a localização. Aprenda a ler três tipos de saída:

  1. Erros de sintaxe: o jq relata linha e coluna, muitas vezes com um trecho próximo. Corrija colchetes e vírgulas primeiro; não suspeite de codificação imediatamente.
  2. Erros de schema: o instancePath do Ajv aponta para a localização dos dados (por exemplo /items/0/price), o schemaPath aponta para a regra (por exemplo #/properties/items/items/properties/price/type), e keyword nomeia a restrição violada.
  3. Erros de formato: quando uma verificação format falha, a mensagem muitas vezes só diz "must match format "email"". Combine-a com instancePath para identificar o campo.

Cole o instancePath no registro de investigação; ele é bem mais útil do que uma parede de JSON. Para arrays, o índice numérico também diz qual registro falhou. Se a chave de negócio for mais intuitiva, adicione uma linha de mapeamento depois, como items[0].id = ORD-042.

Erros comuns de schema, como abusar de additionalProperties: false ou escrever ramos de oneOf que na verdade se sobrepõem, estão cobertos em Armadilhas comuns de geração de JSON Schema. Uma regra de validação errada é mais perigosa do que nenhuma validação, porque cria uma falsa sensação de segurança.

8. Tabela de seleção de ferramentas

Necessidade Ferramenta recomendada Por quê Cuidado
Apenas sintaxe jq empty Zero dependências, rápido, códigos de saída claros Espaços em nomes de arquivo em execuções em lote
Erros de linha/coluna mais amigáveis jsonlint Saída legível Atividade de manutenção limitada
Validação de JSON Schema ajv-cli Ecossistema maduro, erros estruturados Alinhe versões de draft e carregue o suporte a format
Repositório com JSON e YAML misturados check-jsonschema Multi-formato, amigável ao pre-commit Origem do schema e acesso à rede
Feedback rápido antes do commit pre-commit / lint-staged Processa apenas arquivos preparados Os hooks precisam aceitar vários caminhos
Formatação consistente Prettier Amplamente adotado Use --check quando não quiser reescrever arquivos

Uma recomendação final: verifique que o pipeline realmente falha entregando-lhe primeiro um registro deliberadamente inválido. O maior risco não é uma configuração complicada; é uma configuração que nunca entra em vigor. Tratar "ele consegue falhar" como critério de aceitação é mais confiável do que tratar "ele passa" como tal. Para a cadeia de ferramentas de linha de comando mais ampla, continue em Editores, linha de comando e automação.

Perguntas frequentes

Devo usar jq empty ou jsonlint?

Prefira jq empty no CI: zero dependências, inicialização rápida e semântica de código de saída clara. Use jsonlint quando precisar de mensagens de linha/coluna mais amigáveis ou estiver investigando localmente. Ambos fazem apenas validação de sintaxe; nenhum verifica a estrutura dos campos.

Por que o ajv relata uma palavra-chave desconhecida?

Primeiro confirme se o draft $schema do schema corresponde à versão usada pela CLI. Muitos erros de "palavra-chave desconhecida" são palavras-chave de draft novo encontradas por um compilador mais antigo. Se as versões correspondem, verifique se há erro de digitação. --strict=false pode manter schemas legados funcionando, mas não conte com isso a longo prazo para esconder problemas reais.

A validação de schema passa, mas a API ainda falha. Por quê?

Um schema valida estrutura, não semântica de negócio. Por exemplo, status pode ser uma string com um enum legal, enquanto a máquina de estados proíbe passar diretamente de paid para refunded. Regras assim pertencem ao código ou a restrições de banco de dados, não ao JSON Schema.

Como localizo rapidamente o campo que falhou?

Leia o instancePath ou o número de linha da saída da ferramenta. O instancePath do Ajv é um JSON Pointer que aponta diretamente para os dados; o check-jsonschema relata arquivo e linha. Registre o caminho e mapeie-o para a chave de negócio; localizar o problema geralmente leva minutos.

O pre-commit deve rodar uma validação completa?

Não. O pre-commit deve processar apenas arquivos preparados para manter os commits rápidos. Varreduras completas pertencem ao CI ou a jobs agendados. A divisão é simples: execuções locais privilegiam velocidade, o CI privilegia completude.