Curso de Python / Salvar e ler JSON com validação de dados

JSON, de JavaScript Object Notation, é um formato de texto para trocar dados estruturados, como objetos, arrays, strings e números. Em Python, json.load() e dump() trabalham com arquivos, enquanto loads() e dumps() trabalham com texto em memória; interpretar JSON com sucesso não comprova que os dados atendem às regras da aplicação.

Relacionar valores JSON e tipos Python

JSON Python após a leitura usual
Objeto, com pares de nome e valor. dict.
Array. list.
String. str.
Número inteiro ou com parte fracionária. int ou float, respectivamente.
true, false e null. True, False e None.

No texto JSON, nomes de propriedades e strings usam aspas duplas. Aspas simples, comentários de Python e vírgula depois do último item não fazem parte dessa sintaxe. Não tente corrigir uma leitura com eval(): ele interpreta código Python, não é um leitor JSON.

JSON também não guarda todos os tipos Python de maneira idêntica. Uma tupla gravada como array volta como lista; datas e valores Decimal precisam de uma política explícita de conversão. Para dados que exigem preservação de tipos específicos, escolha e documente uma representação, em vez de esperar uma cópia completa de qualquer objeto Python.

Salvar um registro com acento e ler novamente

Use uma pasta separada com dados descartáveis. O programa substitui expenses-demo.json a cada execução. Salve o código completo em json_demo.py, com UTF-8, e execute python json_demo.py no terminal dessa pasta, com o ambiente virtual selecionado se houver. Em macOS ou Ubuntu sem ambiente virtual, use python3 json_demo.py quando necessário. O módulo json faz parte da biblioteca padrão.

import json
from pathlib import Path

data = {"currency": "KRW", "expenses": [{"name": "café", "amount": 8000}]}
path = Path("expenses-demo.json")
path.write_text(json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False), encoding="utf-8")
loaded = json.loads(path.read_text(encoding="utf-8"))
amount = loaded["expenses"][0]["amount"]
if type(amount) is not int or amount < 0:
    raise ValueError("invalid amount")
print(loaded["expenses"][0]["name"])
print(amount)

A saída esperada é:

café
8000

data contém uma lista de despesas dentro de um dicionário. O nome café torna visível o tratamento de caracteres fora de ASCII. json.dumps() transforma a estrutura em uma string JSON, e write_text() grava essa string em UTF-8. Na leitura, read_text() fornece texto para json.loads(), que reconstrói os tipos básicos.

A expressão loaded["expenses"][0]["amount"] acessa o campo da primeira despesa. A condição exige exatamente int e um valor não negativo; por isso exclui bool, apesar de esse tipo ser uma subclasse de int. O exemplo usa uma estrutura conhecida: ele ainda não verifica se todos os caminhos de acesso existem e têm o formato esperado.

Distinguir análise sintática, validação e persistência

Um texto JSON válido pode conter uma lista vazia em expenses, omitir amount ou fornecer uma string no lugar do valor numérico. O acesso direto do exemplo pode então gerar IndexError ou KeyError, ou a validação pode rejeitar o tipo. Num importador geral, valide as chaves obrigatórias, a lista, cada objeto e o intervalo dos valores antes de processá-los.

type(amount) is int expressa o contrato estrito adotado aqui. Trocar por isinstance(amount, int) aceitaria booleanos e outras subclasses. Isso pode ser apropriado em outra interface, mas muda a política. Uma conversão automática também é diferente de uma validação: transformar um valor recebido pode ocultar um formato que deveria ser recusado.

Função Entrada e resultado
load(file) Lê JSON de um objeto de arquivo e retorna dados Python.
loads(text) Interpreta texto JSON em memória.
dump(obj, file) Grava a representação JSON num objeto de arquivo textual.
dumps(obj) Retorna uma string JSON; não cria um arquivo por conta própria.

Python permite certas extensões numéricas na configuração padrão, como NaN e infinitos. allow_nan=False no exemplo os rejeita ao serializar; essa opção não configura uma leitura posterior nem valida todos os campos. Ao receber dados externos estritos, defina também uma política de leitura para constantes não padronizadas e nomes de propriedades repetidos. Por padrão, o leitor conserva o último valor de uma chave repetida. A referência do módulo json e suas opções de codificação e decodificação descreve esses comportamentos.

Testar caracteres, erros e valores inválidos

ensure_ascii=False conserva caracteres como é diretamente na string, em vez de representá-los por escapes Unicode. Ele não escolhe a codificação do arquivo, que continua sendo definida por encoding="utf-8". As duas representações podem reconstruir o mesmo texto depois da leitura. indent=2 organiza a apresentação com recuos e não altera o significado dos dados.

  1. Execute o programa e abra expenses-demo.json num editor UTF-8. Confira o nome café, a lista de despesas e o valor 8000. Repita a execução e confirme que ela substitui o arquivo, sem concatenar documentos.
  2. Na estrutura Python da linha data = ..., substitua somente "amount": 8000 por "amount": -1. O programa grava e lê o JSON, mas depois rejeita o valor com ValueError: invalid amount. Restaure 8000 antes do teste seguinte.
  3. No mesmo ponto da estrutura Python, use "amount": True, com T maiúsculo. O serializador grava true no JSON; depois da leitura ele volta a ser True e a validação também o rejeita. Escrever true diretamente no código Python seria outra situação, normalmente um nome não definido.
  4. Restaure o valor 8000 e troque temporariamente ensure_ascii=False por ensure_ascii=True. O arquivo passa a usar um escape para é, mas a saída continua mostrando café. Restaure a opção original para comparar a apresentação.

Uma sintaxe JSON incorreta produz JSONDecodeError, cuja mensagem informa a posição do problema. Para testar um arquivo editado manualmente, execute apenas uma rotina de leitura separada: rodar este programa completo primeiro recria o arquivo e desfaz a edição. Dados inválidos também já podem ter sido gravados antes de a validação do exemplo falhar; se a política exigir não persistir entradas inválidas, valide a estrutura antes da escrita.

A gravação usada aqui é uma substituição simples num arquivo descartável. Para preservar arquivos importantes, aplique uma política de escrita e recuperação adequada, como discutido na leitura e escrita de texto com fechamento e proteção do original. O fato de a string ser JSON válido não garante integridade de armazenamento nem validade dos valores de negócio.