Trabalhando com CSV e JSON em Python

1. Introdução aos Formatos CSV e JSON

CSV (Comma-Separated Values) é um formato tabular simples onde cada linha representa um registro e os campos são separados por vírgulas. É ideal para dados planos como planilhas, relatórios financeiros e exportações de bancos de dados relacionais.

JSON (JavaScript Object Notation) é um formato hierárquico que suporta estruturas aninhadas como listas e dicionários. É amplamente usado em APIs web, arquivos de configuração e armazenamento de dados complexos.

Quando usar cada formato:
- CSV: dados tabulares simples, grandes volumes de dados numéricos, interoperabilidade com Excel e bancos de dados
- JSON: dados com hierarquia, objetos aninhados, APIs REST, configurações de aplicações

2. Lendo e Escrevendo Arquivos CSV com o Módulo csv

O módulo csv da biblioteca padrão oferece ferramentas robustas para manipulação de arquivos CSV.

Leitura básica com csv.reader()

import csv

# Leitura básica
with open('dados.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo)
    for linha in leitor:
        print(linha)  # Cada linha é uma lista

Escrita básica com csv.writer()

with open('saida.csv', 'w', newline='', encoding='utf-8') as arquivo:
    escritor = csv.writer(arquivo)
    escritor.writerow(['Nome', 'Idade', 'Cidade'])
    escritor.writerow(['Ana', 28, 'São Paulo'])
    escritor.writerow(['Carlos', 35, 'Rio de Janeiro'])

Trabalhando com dicionários: DictReader e DictWriter

# Leitura com DictReader (cabeçalho como chaves)
with open('dados.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.DictReader(arquivo)
    for linha in leitor:
        print(linha['Nome'], linha['Idade'])

# Escrita com DictWriter
with open('saida.csv', 'w', newline='', encoding='utf-8') as arquivo:
    campos = ['Nome', 'Idade', 'Cidade']
    escritor = csv.DictWriter(arquivo, fieldnames=campos)
    escritor.writeheader()
    escritor.writerow({'Nome': 'Ana', 'Idade': 28, 'Cidade': 'São Paulo'})

3. Configurações Avançadas para CSV

Delimitadores personalizados

import csv

# CSV com ponto e vírgula (comum em português)
with open('dados_pt.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo, delimiter=';')
    for linha in leitor:
        print(linha)

# CSV com tabulação
with open('dados_tsv.tsv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo, delimiter='\t')
    # ou use: leitor = csv.reader(arquivo, dialect=csv.excel_tab)

Lidando com citações e valores especiais

with open('dados.csv', 'w', newline='', encoding='utf-8') as arquivo:
    escritor = csv.writer(arquivo, 
                         quoting=csv.QUOTE_ALL,  # Cita todos os campos
                         quotechar='"',          # Caractere de citação
                         escapechar='\\')        # Caractere de escape
    escritor.writerow(['Nome contém, vírgula', 'Valor "especial"', 123])

Tratamento de valores ausentes

with open('dados_incompletos.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo)
    for num_linha, linha in enumerate(leitor, 1):
        # Verifica número de colunas esperado
        if len(linha) < 3:
            print(f"Linha {num_linha} incompleta: {linha}")
            continue
        # Substitui valores vazios por None
        linha_tratada = [None if campo == '' else campo for campo in linha]
        print(linha_tratada)

4. Lendo e Escrevendo Arquivos JSON com o Módulo json

Serialização: json.dumps() e json.dump()

import json

dados = {
    'nome': 'Ana',
    'idade': 28,
    'cidades': ['São Paulo', 'Rio de Janeiro'],
    'ativo': True
}

# Para string
json_string = json.dumps(dados, indent=2, sort_keys=True, ensure_ascii=False)
print(json_string)

# Para arquivo
with open('dados.json', 'w', encoding='utf-8') as arquivo:
    json.dump(dados, arquivo, indent=2, ensure_ascii=False)

Desserialização: json.loads() e json.load()

# De string
json_string = '{"nome": "Carlos", "idade": 35}'
dados = json.loads(json_string)
print(dados['nome'])

# De arquivo
with open('dados.json', 'r', encoding='utf-8') as arquivo:
    dados = json.load(arquivo)
    print(dados)

Parâmetros úteis

# indent: formatação legível
# sort_keys: ordena chaves alfabeticamente
# ensure_ascii=False: preserva caracteres acentuados
json.dumps(dados, indent=4, sort_keys=True, ensure_ascii=False)

5. Trabalhando com Dados JSON Complexos

dados_complexos = {
    'empresa': 'TechCorp',
    'funcionarios': [
        {'nome': 'Ana', 'cargo': 'Dev', 'skills': ['Python', 'Django']},
        {'nome': 'Carlos', 'cargo': 'Analista', 'skills': ['SQL', 'Excel']}
    ],
    'metadados': {
        'versao': 1.0,
        'ultima_atualizacao': '2024-01-15'
    }
}

# Navegação segura
for func in dados_complexos.get('funcionarios', []):
    nome = func.get('nome', 'Desconhecido')
    skills = ', '.join(func.get('skills', []))
    print(f"{nome}: {skills}")

Convertendo objetos Python para JSON

from datetime import datetime

class Pessoa:
    def __init__(self, nome, nascimento):
        self.nome = nome
        self.nascimento = nascimento

def converter_para_json(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, Pessoa):
        return {'nome': obj.nome, 'nascimento': obj.nascimento.isoformat()}
    raise TypeError(f"Tipo não serializável: {type(obj)}")

pessoa = Pessoa('Maria', datetime(1990, 5, 15))
json_string = json.dumps(pessoa, default=converter_para_json, indent=2)
print(json_string)

Convertendo JSON de volta para objetos personalizados

def criar_pessoa(dicionario):
    if 'nascimento' in dicionario:
        dicionario['nascimento'] = datetime.fromisoformat(dicionario['nascimento'])
    return Pessoa(**dicionario)

json_string = '{"nome": "Maria", "nascimento": "1990-05-15T00:00:00"}'
pessoa = json.loads(json_string, object_hook=criar_pessoa)
print(pessoa.nome, pessoa.nascimento)

6. Tratamento de Erros e Validação

Exceções comuns

import json
import csv

# Tratamento de erros em JSON
try:
    with open('dados.json', 'r', encoding='utf-8') as arquivo:
        dados = json.load(arquivo)
except FileNotFoundError:
    print("Arquivo não encontrado")
except json.JSONDecodeError as e:
    print(f"Erro de decodificação JSON: {e}")

# Tratamento de erros em CSV
try:
    with open('dados.csv', 'r', encoding='utf-8') as arquivo:
        leitor = csv.reader(arquivo)
        for linha in leitor:
            if len(linha) != 3:
                raise ValueError(f"Linha inválida: esperado 3 colunas, encontrado {len(linha)}")
except csv.Error as e:
    print(f"Erro no CSV: {e}")

Validação de dados JSON

def validar_usuario(dados):
    obrigatorios = ['nome', 'email', 'idade']
    for campo in obrigatorios:
        if campo not in dados:
            raise ValueError(f"Campo obrigatório ausente: {campo}")
    if not isinstance(dados.get('idade'), (int, float)):
        raise TypeError("Idade deve ser numérica")
    if dados['idade'] < 0 or dados['idade'] > 150:
        raise ValueError("Idade fora do intervalo válido")
    return True

# Uso
try:
    dados = json.loads('{"nome": "Ana", "email": "ana@email.com", "idade": 28}')
    validar_usuario(dados)
    print("Dados válidos")
except (json.JSONDecodeError, ValueError, TypeError) as e:
    print(f"Erro de validação: {e}")

7. Conversão Entre CSV e JSON

CSV para JSON

import csv
import json

def csv_para_json(arquivo_csv, arquivo_json):
    with open(arquivo_csv, 'r', encoding='utf-8') as csv_file:
        leitor = csv.DictReader(csv_file)
        dados = list(leitor)

    with open(arquivo_json, 'w', encoding='utf-8') as json_file:
        json.dump(dados, json_file, indent=2, ensure_ascii=False)

    print(f"Convertido {arquivo_csv} para {arquivo_json}")

# Uso
csv_para_json('entrada.csv', 'saida.json')

JSON para CSV (achatamento de estruturas)

def json_para_csv(arquivo_json, arquivo_csv):
    with open(arquivo_json, 'r', encoding='utf-8') as json_file:
        dados = json.load(json_file)

    if not dados:
        return

    # Achata estruturas aninhadas
    dados_planos = []
    for item in dados:
        item_plano = {}
        for chave, valor in item.items():
            if isinstance(valor, (list, dict)):
                item_plano[chave] = json.dumps(valor, ensure_ascii=False)
            else:
                item_plano[chave] = valor
        dados_planos.append(item_plano)

    with open(arquivo_csv, 'w', newline='', encoding='utf-8') as csv_file:
        campos = dados_planos[0].keys()
        escritor = csv.DictWriter(csv_file, fieldnames=campos)
        escritor.writeheader()
        escritor.writerows(dados_planos)

    print(f"Convertido {arquivo_json} para {arquivo_csv}")

# Uso
json_para_csv('entrada.json', 'saida.csv')

Script completo de conversão bidirecional

def converter(formato_origem, arquivo_origem, arquivo_destino):
    if formato_origem == 'csv':
        csv_para_json(arquivo_origem, arquivo_destino)
    elif formato_origem == 'json':
        json_para_csv(arquivo_origem, arquivo_destino)
    else:
        print("Formato não suportado. Use 'csv' ou 'json'.")

# Exemplo de uso
converter('csv', 'dados.csv', 'dados.json')
converter('json', 'dados.json', 'dados_convertido.csv')

8. Boas Práticas e Performance

Trabalhando com arquivos grandes

# Leitura sob demanda (recomendado para arquivos grandes)
with open('grande.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo)
    next(leitor)  # Pula cabeçalho
    for linha in leitor:
        processar_linha(linha)  # Processa uma linha por vez

# Para JSON grande, use ijson (biblioteca externa)
# import ijson
# with open('grande.json', 'r', encoding='utf-8') as arquivo:
#     for item in ijson.items(arquivo, 'item'):
#         processar_item(item)

Codificação de caracteres

# Sempre especificar encoding
with open('dados.csv', 'r', encoding='utf-8') as arquivo:
    leitor = csv.reader(arquivo)

# Para arquivos com acentos, ensure_ascii=False é essencial
with open('dados.json', 'w', encoding='utf-8') as arquivo:
    json.dump(dados, arquivo, ensure_ascii=False, indent=2)

Uso de with open() para gerenciamento de contexto

# Garantia de fechamento automático do arquivo
with open('dados.csv', 'r', encoding='utf-8') as arquivo:
    # Processamento aqui
    pass  # Arquivo é fechado automaticamente ao sair do bloco

# Equivalente sem with (não recomendado)
arquivo = open('dados.csv', 'r', encoding='utf-8')
try:
    # Processamento
    pass
finally:
    arquivo.close()

Referências