Convertendo CSV para JSON: Métodos e Armadilhas

· 12 min de leitura

Índice

Entendendo os Fundamentos da Conversão de CSV para JSON

Converter CSV (Valores Separados por Vírgula) para JSON (Notação de Objeto JavaScript) é uma das tarefas de transformação de dados mais comuns que os desenvolvedores encontram. Embora o processo pareça simples para conjuntos de dados simples, entender a mecânica fundamental garante que você evite bugs sutis que podem corromper seus dados.

Arquivos CSV seguem uma estrutura tabular onde a primeira linha normalmente contém cabeçalhos de coluna. Cada linha subsequente representa um registro com valores correspondentes a esses cabeçalhos. JSON, por outro lado, usa uma estrutura hierárquica de chave-valor que é mais flexível e expressiva.

A transformação básica mapeia cabeçalhos CSV para chaves JSON, com cada linha de dados se tornando um objeto em um array JSON:

CSV:
name,age,city
Alice,30,NYC
Bob,25,LA

JSON:
[
  {"name":"Alice","age":"30","city":"NYC"},
  {"name":"Bob","age":"25","city":"LA"}
]

Esta correspondência um-para-um funciona perfeitamente para estruturas de dados planas. No entanto, cenários do mundo real introduzem complicações: valores ausentes, tipos de dados inconsistentes, caracteres especiais e a necessidade de estruturas aninhadas, todos exigem tratamento cuidadoso.

Dica profissional: Sempre inspecione as primeiras linhas do seu arquivo CSV antes da conversão. Procure por delimitadores inconsistentes, campos entre aspas e quebras de linha inesperadas que possam causar erros de análise.

Por Que Converter CSV para JSON?

JSON se tornou o padrão de fato para APIs web e desenvolvimento de aplicações modernas. Aqui está o porquê de desenvolvedores frequentemente precisarem converter dados CSV:

Desafios de Conversão de Tipos de Dados

Um dos desafios mais significativos ao converter CSV para JSON é preservar tipos de dados. CSV é fundamentalmente um formato de texto—cada valor é armazenado como uma string. Isso cria problemas quando seus dados contêm números, datas, booleanos ou valores nulos que precisam ser representados corretamente em JSON.

Analisando Dados Numéricos

Considere um arquivo CSV contendo dados de inventário de produtos. Sem conversão de tipo adequada, valores numéricos como preços e quantidades permanecem strings, quebrando cálculos e comparações em sua aplicação.

import csv
import json

def parse_csv_with_types(filename):
    def try_numeric(val):
        # Handle empty values
        if not val or val.strip() == '':
            return None
        
        # Try integer conversion first
        try:
            return int(val)
        except ValueError:
            pass
        
        # Try float conversion
        try:
            return float(val)
        except ValueError:
            return val
    
    with open(filename, 'r') as f:
        reader = csv.DictReader(f)
        data = []
        for row in reader:
            typed_row = {k: try_numeric(v) for k, v in row.items()}
            data.append(typed_row)
    
    return json.dumps(data, indent=2)

Esta abordagem tenta converter cada valor para um inteiro primeiro, depois um float, e finalmente o mantém como uma string se ambas as conversões falharem. O resultado é JSON devidamente tipado que preserva precisão numérica.

Tratamento de Data e Hora

A análise de datas apresenta desafios únicos porque arquivos CSV podem conter datas em inúmeros formatos: ISO 8601, formato americano (MM/DD/AAAA), formato europeu (DD/MM/AAAA), ou formatos personalizados. Sua lógica de conversão precisa lidar com essas variações:

from datetime import datetime

def parse_date(val):
    date_formats = [
        '%Y-%m-%d',           # ISO format
        '%m/%d/%Y',           # US format
        '%d/%m/%Y',           # European format
        '%Y-%m-%d %H:%M:%S',  # ISO with time
        '%m/%d/%Y %I:%M %p'   # US with 12-hour time
    ]
    
    for fmt in date_formats:
        try:
            return datetime.strptime(val, fmt).isoformat()
        except ValueError:
            continue
    
    return val  # Return original if no format matches

Dica rápida: Ao lidar com datas de múltiplas fontes, padronize no formato ISO 8601 (AAAA-MM-DD) em sua saída JSON. Este formato é inequívoco e ordena corretamente como uma string.

Conversão de Valores Booleanos e Nulos

Arquivos CSV frequentemente representam valores booleanos como "true"/"false", "yes"/"no", "1"/"0", ou variações similares. Células vazias podem representar valores nulos, mas também podem ser strings vazias. Sua lógica de conversão deve lidar com essas ambiguidades:

Valor CSV Tipo Pretendido Erro Comum JSON Correto
true Booleano "true" (string) true
1 Booleano ou Inteiro "1" (string) true ou 1
(vazio) Nulo "" (string vazia) null
N/A Nulo "N/A" (string) null

Use nosso Analisador e Visualizador de CSV para visualizar como seus dados serão interpretados antes da conversão.

Lidando com Caracteres Especiais e Codificações

Caracteres especiais e problemas de codificação causam mais falhas de conversão do que qualquer outro problema. Arquivos CSV podem conter vírgulas dentro de campos, novas linhas em texto, aspas ou caracteres não-ASCII que quebram a lógica de análise ingênua.

Campos Entre Aspas e Caracteres Escapados

O padrão CSV (RFC 4180) especifica que campos contendo vírgulas, aspas ou novas linhas devem ser colocados entre aspas duplas. Dentro de campos entre aspas, as próprias aspas devem ser escapadas duplicando-as:

name,description,price
"Widget A","A simple, reliable widget",19.99
"Widget ""Pro""","The ""best"" widget available",49.99

Um analisador CSV robusto lida com esses casos automaticamente. Se você está escrevendo seu próprio analisador, precisa rastrear se está dentro de um campo entre aspas e lidar com sequências de escape corretamente.

Problemas de Codificação de Caracteres

Arquivos CSV podem ser codificados em UTF-8, Latin-1, Windows-1252 ou outros conjuntos de caracteres. Codificação incompatível causa texto distorcido, especialmente para caracteres não-ingleses:

Sempre especifique a codificação explicitamente ao ler arquivos CSV:

import csv
import json

def convert_with_encoding(filename, encoding='utf-8'):
    try:
        with open(filename, 'r', encoding=encoding) as f:
            reader = csv.DictReader(f)
            data = list(reader)
            return json.dumps(data, ensure_ascii=False, indent=2)
    except UnicodeDecodeError:
        # Try alternative encodings
        for alt_encoding in ['latin-1', 'windows-1252', 'utf-16']:
            try:
                with open(filename, 'r', encoding=alt_encoding) as f:
                    reader = csv.DictReader(f)
                    data = list(reader)
                    return json.dumps(data, ensure_ascii=False, indent=2)
            except UnicodeDecodeError:
                continue
        raise ValueError(f"Could not decode {filename} with any known encoding")

Dica profissional: O parâmetro ensure_ascii=False em json.dumps() preserva caracteres Unicode na saída em vez de escapá-los como sequências \uXXXX, tornando o JSON mais legível.

Marcas de Ordem de Byte (BOM)

Algumas aplicações, particularmente o Microsoft Excel, adicionam uma Marca de Ordem de Byte (BOM) no início de arquivos UTF-8. Este caractere invisível pode fazer com que o primeiro nome de campo seja lido incorretamente. O parâmetro de codificação do Python lida com isso automaticamente com 'utf-8-sig':

with open(filename, 'r', encoding='utf-8-sig') as f:
    reader = csv.DictReader(f)

Criando Estruturas JSON Aninhadas a partir de CSV Plano

CSV é inerentemente plano—representa tabelas bidimensionais. JSON suporta estruturas hierárquicas com objetos e arrays aninhados. Converter dados CSV planos em JSON aninhado requer design cuidadoso e lógica adicional.

Agrupando Dados Relacionados

Considere um arquivo CSV contendo pedidos de clientes onde cada linha tem informações do cliente repetidas para cada pedido:

customer_id,customer_name,order_id,product,quantity
101,Alice,1001,Widget,5
101,Alice,1002,Gadget,3
102,Bob,1003,Widget,2

Uma estrutura JSON melhor agrupa pedidos sob cada cliente:

[
  {
    "customer_id": 101,
    "customer_name": "Alice",
    "orders": [
      {"order_id": 1001, "product": "Widget", "quantity": 5},
      {"order_id": 1002, "product": "Gadget", "quantity": 3}
    ]
  },
  {
    "customer_id": 102,
    "customer_name": "Bob",
    "orders": [
      {"order_id": 1003, "product": "Widget", "quantity": 2}
    ]
  }
]

Aqui está como implementar esta transformação:

import csv
import json
from collections import defaultdict

def csv_to_nested_json(filename):
    customers = defaultdict(lambda: {"orders": []})
    
    with open(filename, 'r') as f:
        reader = csv.DictReader(f)
        for row in reader:
            customer_id = int(row['customer_id'])
            
            # Set customer info if not already set
            if 'customer_id' not in customers[customer_id]:
                customers[customer_id]['customer_id'] = customer_id
                customers[customer_id]['customer_name'] = row['customer_name']
            
            # Add order
            customers[customer_id]['orders'].append({
                'order_id': int(row['order_id']),
                'product': row['product'],
                'quantity': int(row['quantity'])
            })
    
    return json.dumps(list(customers.values()), indent=2)

Notação de Ponto para Chaves Aninhadas

Outra abordagem usa notação de ponto em cabeçalhos CSV para indicar aninhamento:

name,address.street,address.city,address.zip
Alice,123 Main St,NYC,10001
Bob,456 Oak Ave,LA,90001

Isso converte para:

[
  {
    "name": "Alice",
    "address": {
      "street": "123 Main St",
      "city": "NYC",
      "zip": "10001"
    }
  }
]

A implementação requer analisar as chaves do cabeçalho e construir dicionários aninhados:

def set_nested_value(obj, path, value):
    keys = path.split('.')
    for key in keys[:-1]:
        obj = obj.setdefault(key, {})
    obj[keys[-1]] = value

def csv_to_nested_with_dots(filename):
    with open(filename, 'r') as f:
        reader = csv.DictReader(f)
        data = []
        for row in reader:
            obj = {}
            for key, value in row.items():
                set_nested_value(obj, key, value)
            data.append(obj)
    return json.dumps(data, indent=2)

Escalando com Arquivos Grandes e Otimização de Desempenho

Converter arquivos CSV pequenos é trivial, mas sistemas de produção frequentemente lidam com arquivos contendo milhões de linhas. Carregar um CSV inteiro de vários gigabytes na memória causa travamentos e problemas de desempenho.

Processamento em Streaming

Em vez de carregar o arquivo inteiro na memória, processe-o linha por linha e escreva JSON incrementalmente:

import csv
import json

def stream_csv_to_json(input_file, output_file):
    with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
        reader = csv.DictReader(infile)
        
        outfile.write('[\n')
        first = True
        
        for row in reader:
            if not first:
                outfile.write(',\n')
            first = False
            
            json.dump(row, outfile)
        
        outfile.write('\n]')

Esta abordagem mantém uso constante de memória independentemente do tamanho do arquivo. O trade-off é que você não pode facilmente criar estruturas aninhadas ou realizar agregações que requerem ver todos os dados de uma vez.

Processamento em Blocos

Para operações que requerem alguma agregação mas não o conjunto de dados inteiro, processe o arquivo em blocos:

def process_in_chunks(filename, chunk_size=10000):
    with open(filename, 'r') as f:
        reader = csv.DictReader(f)
        chunk = []
        
    

We use cookies for analytics. By continuing, you agree to our Privacy Policy.