Todos os artigos
JSON Schemaextração de dadosdesenvolvedores

JSON Schema para extração de dados de documentos: guia prático

O que é JSON Schema, por que definir campos e tipos antes de extrair, exemplo completo para conta de energia, boas práticas e o que acontece na validação.

Equipe Colunar · Publicado em · 8 min de leitura

JSON Schema é uma forma de descrever, em JSON, como um JSON válido deve ser: quais campos existem, de que tipo são, quais são obrigatórios e que formatos aceitam. Na extração de dados de documentos, ele serve como contrato: você define o resultado antes de processar o primeiro arquivo, e cada registro extraído é validado contra esse contrato.

Isso muda a natureza do trabalho. Sem schema, o modelo devolve "o que achou" e alguém precisa conferir tipos, formatos e campos faltando à mão. Com schema, o que não bate é rejeitado e reportado, e o que passa já está no formato que o próximo sistema espera.

Este guia mostra o essencial do JSON Schema (draft 2020-12), um exemplo completo para conta de energia, boas práticas específicas para extração e como isso aparece em uma ferramenta como o Colunar.

O que é JSON Schema (draft 2020-12)

JSON Schema é uma especificação para descrever a estrutura de documentos JSON. O draft 2020-12 é a versão atual, suportada por validadores em praticamente todas as linguagens (Python, JavaScript, Go, Java, .NET). Um schema é ele mesmo um objeto JSON com palavras-chave como type, properties, required, format, enum e additionalProperties. Um validador recebe o schema e um documento e responde se o documento está em conformidade, listando cada violação com o caminho do campo.

Por que definir o resultado antes de extrair

Extração sem schema é uma conversa aberta com o modelo: "me diga o que tem nessa conta de energia". A resposta pode vir com o consumo como "245 kWh" num arquivo e 245 no outro; a data como "12/03/2026" numa e "2026-03-12" na próxima; um campo valor aqui e valor_total ali. Cada variação quebra o próximo passo da pipeline.

Definir o schema antes resolve quatro coisas:

  • Campos. Só os que você precisa, com nomes fixos. Nada de campos que aparecem ou somem entre registros.
  • Tipos. consumo_kwh é number, não string. unidade_consumidora é string, mesmo que só tenha dígitos, porque zeros à esquerda importam.
  • Obrigatórios. Se valor_total não veio, o registro está incompleto e você quer saber agora, não descobrir na soma da planilha.
  • Formatos e enums. Datas em format: "date" (ISO 8601, YYYY-MM-DD). Um campo tipo_tarifa restrito a ["convencional", "branca"] em vez de texto livre que precisa ser normalizado depois.

O schema também serve de instrução para o modelo. Um modelo de linguagem que recebe "preencha este objeto com estes tipos" erra menos de formato do que um que recebe "extraia os dados". E as description de cada campo viram contexto para desambiguar rótulos parecidos.

Exemplo completo: schema para uma conta de energia

Um registro por conta, com os campos que um inventário de emissões ou um controle de despesas normalmente precisa:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Conta de energia",
  "type": "object",
  "properties": {
    "distribuidora": {
      "type": "string",
      "description": "Nome da distribuidora que emitiu a conta, como impresso no cabeçalho."
    },
    "unidade_consumidora": {
      "type": "string",
      "description": "Código da unidade consumidora (UC), com zeros à esquerda."
    },
    "periodo_inicio": {
      "type": "string",
      "format": "date",
      "description": "Data inicial do período de leitura, em YYYY-MM-DD."
    },
    "periodo_fim": {
      "type": "string",
      "format": "date",
      "description": "Data final do período de leitura, em YYYY-MM-DD."
    },
    "consumo_kwh": {
      "type": "number",
      "minimum": 0,
      "description": "Consumo faturado no período, em kWh."
    },
    "valor_total": {
      "type": "number",
      "minimum": 0,
      "description": "Valor total a pagar, em reais, com ponto decimal."
    }
  },
  "required": [
    "distribuidora",
    "unidade_consumidora",
    "periodo_inicio",
    "periodo_fim",
    "consumo_kwh",
    "valor_total"
  ],
  "additionalProperties": false
}

Um registro válido:

{
  "distribuidora": "Distribuidora Exemplo S.A.",
  "unidade_consumidora": "0012345678",
  "periodo_inicio": "2026-08-05",
  "periodo_fim": "2026-09-04",
  "consumo_kwh": 1240,
  "valor_total": 987.65
}

E um registro inválido, com quatro problemas:

{
  "distribuidora": "Distribuidora Exemplo S.A.",
  "unidade_consumidora": "0012345678",
  "periodo_inicio": "05/08/2026",
  "periodo_fim": "2026-09-04",
  "consumo_kwh": "1.240 kWh",
  "vencimento": "2026-09-20"
}

O validador apontaria: periodo_inicio não está no formato date; consumo_kwh é string, não number; vencimento não é permitido (additionalProperties: false); e valor_total, obrigatório, está ausente. Quatro violações, cada uma com o caminho do campo.

Boas práticas ao escrever schemas para extração

Schemas para extração têm particularidades em relação a schemas de API. O "produtor" é um modelo que lê um documento, e o schema é ao mesmo tempo validação e instrução.

  • Nomes em snake_case. consumo_kwh, valor_total, periodo_inicio. Consistente, legível e compatível com a maioria das ferramentas de dados sem renomear colunas. Evite acentos e espaços nos nomes de campos.
  • description curta e útil. Uma frase que ajuda quem lê o schema e o modelo que preenche: "Data final do período de leitura" desambigua de "data de vencimento". Não escreva parágrafos; se a instrução é longa, ela pertence às instruções de leitura do playbook, não ao schema.
  • Evite campos derivados. valor_por_kwh, dias_no_periodo, consumo_medio_diario podem ser calculados depois a partir dos campos base. Pedir ao modelo para calcular é pedir para ele errar aritmética e esconder o erro num campo que parece extraído.
  • Não exagere em required. Marque como obrigatório o que, se faltar, invalida o registro. Um campo que existe só em algumas distribuidoras (bandeira tarifária, por exemplo) deve ser opcional; caso contrário, contas legítimas serão rejeitadas.
  • Um objeto por registro; arrays aninhadas só quando necessário. Se cada conta é uma linha na planilha, o schema é um objeto plano. Arrays fazem sentido para itens de uma nota fiscal (vários produtos por documento), mas aí cada item vira uma linha na exportação e o cabeçalho se repete. Quando puder, prefira dois schemas planos a um schema aninhado profundo.
  • additionalProperties: false. Impede que o modelo invente campos extras que ninguém pediu. Se um campo novo é útil, adicione ao schema de propósito.
  • Tipos numéricos como number, não string. Para o modelo, isso significa devolver 987.65, não "R$ 987,65". A conversão de vírgula para ponto acontece na extração, e a validação garante que aconteceu.
  • enum para categorias fechadas. forma_pagamento: ["boleto", "pix", "cartao", "debito_automatico"] evita doze grafias de "PIX".
  • minimum: 0 onde faz sentido. Consumo e valor negativos são sinal de leitura errada, e o validador pega isso antes da planilha.

Validação: o que acontece com um registro inválido

Esta é a parte que define se o schema serve para alguma coisa. Há dois comportamentos possíveis diante de um registro que não passa:

  1. Corrigir em silêncio. A ferramenta ajusta o tipo, preenche o campo faltante com um valor padrão ou descarta o campo extra, e o registro segue como se estivesse certo. Parece conveniente e é perigoso: o erro desaparece do relatório e reaparece meses depois.
  2. Rejeitar e reportar. O registro é marcado como inválido, com a lista de violações e o local de origem (arquivo, página, tabela). Nada é descartado, e nada entra na tabela sem estar em conformidade.

O segundo comportamento é o correto para dados que vão alimentar um fechamento, um cálculo de emissões ou uma conciliação. Ele transfere para uma pessoa a decisão sobre cada exceção, com a informação necessária para decidir rápido.

Na prática, o fluxo fica assim: o modelo lê o documento e produz um objeto; o validador confere contra o schema; registros válidos vão para a tabela; registros inválidos aparecem com aviso, apontando arquivo, página e campos com problema; quem revisa abre a origem, corrige ou ajusta a instrução, e reprocessa. A validação não substitui a revisão. Ela a foca.

Como isso funciona no Colunar

No Colunar, o schema é a base de cada playbook. Há dois caminhos:

  • Editor de campos. Você adiciona campos com nome, tipo, se é obrigatório, contexto e instruções de leitura. O editor gera o JSON Schema por baixo; você não precisa escrever JSON para começar.
  • Modo JSON. Para quem já tem um schema (ou quer controle total), é possível colar o próprio JSON Schema. O exemplo de conta de energia acima funciona como está.

Em ambos os casos, cada registro extraído é validado contra o schema. Problemas são reportados por arquivo e por página ou tabela, nunca descartados em silêncio, e a origem (página, aba, linha) fica gravada junto com o dado. A exportação sai em Excel, CSV, JSON ou Markdown.

Uma restrição a saber: o schema aceita apenas referências locais, ou seja, $ref apontando para definições dentro do próprio documento (#/$defs/...). Referências a URLs externas não são resolvidas. Se você reutiliza definições entre schemas, copie-as para $defs.

Se o seu problema é entender a diferença entre isso e OCR tradicional, veja OCR ou IA: qual a diferença para extrair dados. Se está montando os campos para um caso de contabilidade, os modelos prontos de playbook (notas fiscais e faturas, contas de energia e água, recibos e despesas, extratos bancários) são um ponto de partida que já vem com schema.

Perguntas frequentes

Preciso saber JSON Schema para usar uma ferramenta de extração?

Não necessariamente. Ferramentas com editor de campos geram o schema a partir de nome, tipo e obrigatoriedade. Saber o básico ajuda a entender os avisos de validação e a escrever descriptions melhores, mas não é pré-requisito.

Devo usar format: "date" ou uma string livre para datas?

Use format: "date" (YYYY-MM-DD). Ele força um formato único, ordenável e reconhecido por qualquer sistema. Um detalhe do draft 2020-12: em validadores genéricos, format é anotação por padrão e precisa ser habilitado como asserção; confira a configuração do validador que você usa. Datas em texto livre chegam em três ou quatro grafias e precisam ser normalizadas depois, justamente o trabalho que o schema deveria evitar.

O que faço com campos que só existem em alguns documentos?

Declare-os em properties, mas deixe fora de required. Assim, quando existem, são validados pelo tipo; quando não existem, o registro continua válido. Reserve required para o que invalida o registro se faltar.

Um schema com arrays aninhadas funciona?

Funciona, mas complica a exportação para planilha: cada item da array vira uma linha e o cabeçalho se repete. Para itens de nota fiscal isso é inevitável; para o resto, prefira um objeto plano por registro.

Se você já tem um schema, cole-o no modo JSON de um playbook e processe alguns documentos reais para ver a validação em ação. Comece grátis com 20 créditos.

Continue lendo