Converta uma lista JSON para CSV e verifique campos ausentes
Converta os dados em uma tabela diferenciando chaves ausentes, valores null e strings vazias, e crie um relatório separado de problemas.
Conteúdo verificado 2026.09.19Inclui arquivos de exemplo
Ver o sumário
A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English
Para quem éIniciantes em Python que querem visualizar um arquivo JSON como tabela e encontrar onde há dados ausentes
Preparação
Tenha Python 3.12 ou posterior e um terminal prontos. Nenhum pacote adicional é necessário.
Extraia o ZIP e abra um terminal na pasta que contém example.py e sample.json.
Comece apenas com os dados sintéticos de items incluídos. Mantenha os arquivos originais separados.
01Por que diferenciar campos ausentes de valores vazios?
Em JSON, uma chave ausente, um valor null e uma string vazia são estados diferentes. Como todos eles podem parecer células em branco no CSV, este exemplo registra o estado original em um arquivo field_issues.csv separado. Ele não preenche valores substitutos arbitrários.
As colunas esperadas são item, quantity e unit. Os valores 0, false e um único espaço não são tratados como ausentes. O código primeiro verifica se a chave existe e depois verifica o estado do valor.
Há 4 registros. O segundo não tem a chave unit e o terceiro não tem a chave quantity. No quarto, quantity é null e unit é uma string vazia. Os nomes de item e as units são valores reais de entrada, portanto permanecem iguais nas versões traduzidas.
Os caminhos relativos no comando são resolvidos a partir da pasta atual do terminal. Antes de executar, navegue até a pasta que contém example.py. A pasta de saída ainda não deve existir, e sua pasta pai já deve existir. Se outputs já existir, escolha um novo nome.
Uma nova pasta é criada depois que a entrada é validada. O código não esvazia nem sobrescreve uma pasta de saída existente e não modifica o JSON de entrada.
As contagens de problemas se referem a células com problemas, não ao número de linhas. A quarta linha tem dois problemas, elevando o total de problemas relatados para 4. Verifique primeiro o CSV em um editor de texto. Dados externos arbitrários podem ser interpretados automaticamente por softwares de planilha, e este código não sanitiza formula strings.
05Código completo e fluxo de processamento
Somente objects dentro de um JSON array são permitidos. Nested objects ou arrays, columns desconhecidas, duplicate JSON keys, NaN e Infinity são rejeitados. O código lê decimal numbers como Decimal para evitar conversão desnecessária para binary floating point. Arquivos de entrada maiores que 5 MiB não são processados.
example.py
#!/usr/bin/env python3
"""Convert a small local JSON array to CSV and a field-issue report.
Target: Python 3.12+, standard library only. No network access.
All output goes to a NEW directory; existing directories/files are refused.
"""
from __future__ import annotations
import argparse
import csv
import io
import json
import sys
from decimal import Decimal
from pathlib import Path
DEFAULT_FIELDS = ("item", "quantity", "unit")
MAX_INPUT_BYTES = 5 * 1024 * 1024
ISSUE_FIELDS = ("row_number", "field", "issue")
class InputError(ValueError):
"""The input does not satisfy this example's data contract."""
def unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]:
result: dict[str, object] = {}
for key, value in pairs:
if key in result:
raise InputError("Duplicate key in a JSON object.")
result[key] = value
return result
def reject_constant(token: str) -> object:
raise InputError(f"Non-standard JSON number: {token}.")
def csv_bytes(fields: tuple[str, ...], rows: list[dict]) -> bytes:
buffer = io.StringIO(newline="")
writer = csv.DictWriter(buffer, fieldnames=fields, lineterminator="\r\n")
writer.writeheader()
writer.writerows(rows)
return buffer.getvalue().encode("utf-8")
def prepare_outputs(source: Path, fields: tuple[str, ...]) -> tuple[dict, dict]:
if not fields or len(fields) != len(set(fields)):
raise InputError("--fields must contain at least one unique field name.")
if any(not field.strip() for field in fields):
raise InputError("Field names must not be empty or whitespace-only.")
# Read-only, bounded input. Do not modify the source, even on failure.
with source.open("rb") as stream:
raw = stream.read(MAX_INPUT_BYTES + 1)
if len(raw) > MAX_INPUT_BYTES:
raise InputError("Input exceeds the example's 5 MiB limit.")
records = json.loads(
raw.decode("utf-8-sig"),
object_pairs_hook=unique_object,
parse_float=Decimal,
parse_constant=reject_constant,
)
if not isinstance(records, list):
raise InputError("Top-level JSON value must be an array.")
rows: list[dict[str, str]] = []
issues: list[dict[str, object]] = []
counts = {"missing_key": 0, "null": 0, "empty_string": 0}
missing_rows: set[int] = set()
allowed = set(fields)
for row_number, record in enumerate(records, start=1):
if not isinstance(record, dict):
raise InputError(f"Record {row_number} must be an object.")
if set(record) - allowed:
raise InputError(
f"Record {row_number} has unlisted keys; include them in --fields."
)
row: dict[str, str] = {}
for field in fields:
issue = None
if field not in record:
issue = "missing_key"
missing_rows.add(row_number)
value = None
else:
value = record[field]
if value is None:
issue = "null"
elif value == "":
issue = "empty_string"
if issue is not None:
counts[issue] += 1
issues.append({"row_number": row_number, "field": field, "issue": issue})
row[field] = ""
elif isinstance(value, str):
row[field] = value
elif isinstance(value, bool):
row[field] = "true" if value else "false"
elif isinstance(value, (int, Decimal)):
row[field] = str(value)
else:
raise InputError(
f"Record {row_number} contains an unsupported nested value."
)
rows.append(row)
summary = {
"status": "complete",
"columns": list(fields),
"input_rows": len(records),
"output_rows": len(rows),
"missing_key_cells": counts["missing_key"],
"rows_with_missing_keys": len(missing_rows),
"null_cells": counts["null"],
"empty_string_cells": counts["empty_string"],
"reported_issue_cells": len(issues),
}
# Render and UTF-8-encode EVERYTHING before creating the output directory.
# summary.json is written last and serves as a completion record.
outputs = {
"converted.csv": csv_bytes(fields, rows),
"field_issues.csv": csv_bytes(ISSUE_FIELDS, issues),
"summary.json": (json.dumps(summary, ensure_ascii=False, indent=2) + "\n").encode("utf-8"),
}
return outputs, summary
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input", type=Path, help="Local UTF-8 JSON file")
parser.add_argument("--output-dir", type=Path, required=True, help="NEW directory; parent must exist")
parser.add_argument("--fields", nargs="+", default=list(DEFAULT_FIELDS), help="Expected CSV columns, in order")
args = parser.parse_args(argv)
if sys.version_info < (3, 12):
print("E_VERSION: Python 3.12 or newer is required.", file=sys.stderr)
return 2
try:
outputs, summary = prepare_outputs(args.input, tuple(args.fields))
except json.JSONDecodeError as exc:
print(f"E_INPUT: Invalid JSON at line {exc.lineno}, column {exc.colno}.", file=sys.stderr)
return 2
except (ValueError, ArithmeticError, RecursionError) as exc:
print(f"E_INPUT: {exc}", file=sys.stderr)
return 2
except OSError as exc:
print(f"E_READ: Cannot read input ({exc.__class__.__name__}).", file=sys.stderr)
return 4
try:
args.output_dir.mkdir(exist_ok=False)
except FileExistsError:
print("E_OUTPUT_EXISTS: Output path already exists; choose a new directory.", file=sys.stderr)
return 3
except OSError as exc:
print(f"E_WRITE: Cannot create output directory ({exc.__class__.__name__}).", file=sys.stderr)
return 4
try:
for name, payload in outputs.items():
with (args.output_dir / name).open("xb") as stream:
stream.write(payload)
except OSError as exc:
print(
f"E_WRITE: Incomplete NEW output directory ({exc.__class__.__name__}); "
"do not use its results. Inspect it and choose a new directory.",
file=sys.stderr,
)
return 4
print(
f"OK: rows={summary['output_rows']}; "
f"missing_key_cells={summary['missing_key_cells']}; "
f"null_cells={summary['null_cells']}; "
f"empty_string_cells={summary['empty_string_cells']}; "
f"issue_cells={summary['reported_issue_cells']}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
06Execute os testes incluídos
bash
python -B -X utf8 -m unittest -v test_example
Os testes usam temporary folders para verificar conversão normal, distinção entre valores ausentes e vazios, entrada inválida e proteção da saída existente. Verifique se o resultado final dos 22 tests é OK. Passar nos testes não significa que todos os formatos de dados do mundo real sejam suportados.
07Encontre a causa pela mensagem de erro
Mensagem
O que verificar
E_INPUT
Verifique JSON syntax, column names, value types e input size.
E_OUTPUT_EXISTS
Especifique um novo output folder name.
E_READ
Verifique input path e read permissions.
E_WRITE
Verifique write permissions e o estado do disk. Não use uma output incompleta.
08Escopo e limitações
Este exemplo é destinado a arquivos pequenos e mantém todo o conteúdo de entrada e saída na memória. Ele não achata nested JSON, não converte datas, não infere columns automaticamente nem preenche missing values. Ao corrigir erros, trabalhe em uma cópia de prática do arquivo original.
Se ocorrer um disk error durante a gravação da saída, apenas alguns arquivos podem permanecer. Verifique tanto o success message quanto o conteúdo dos três arquivos. Este exemplo não verifica o comportamento de conversão automática de softwares de planilha ao abrir arquivos CSV.
Registro de execução e verificação
2026-09-19 · Windows 11 · CPython 3.12.14 · Biblioteca padrão
Foi confirmado que 22 tests passaram em uma revisão independente
Foi confirmado que o código do artigo corresponde ao código disponível para download
Foram confirmadas 4 sample rows e 4 reported issues
Foi confirmado o bloqueio de uma output folder existente e a preservação do input original
Limites da verificação
O autor original realizou uma verificação separada no Linux com CPython 3.13.5.
Nem todos os dados do mundo real ou interpretações automáticas de softwares de planilha foram verificados.
O código de exemplo, os nomes de arquivos e as chaves de entrada são mantidos como no original. Consulte também os comandos e os procedimentos de conferência do texto traduzido.
Material de prática original · Guarde os arquivos originais separadamente antes de executar.
Fontes de referência
As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.
Percorra subpastas e registre caminhos, extensões, tamanhos e datas de modificação dos arquivos em uma tabela. Comece com 4 pequenos arquivos de exemplo, mantendo os originais e os resultados existentes intactos.
Combine em ordem arquivos CSV com a mesma estrutura de colunas e adicione uma coluna source_file. Use conjuntos de dados pequenos para verificar nomes de itens com vírgulas, colunas ausentes e saídas já existentes.