Saltar al contenido

lección 4

CSV y JSON en profundidad: los formatos que ya conoces

Refuerza CSV y JSON desde la perspectiva de datos profesionales. Problemas reales, edge cases y buenas prácticas.

50 min

Ya sabes leer y escribir CSV y JSON con Python — lo hiciste en la Skill anterior. Pero usarlos en un contexto profesional de datos es otra historia. CSV parece simple hasta que te encuentras con un campo que contiene una coma dentro del texto. JSON parece perfecto hasta que un archivo de 2 GB tarda 10 minutos en parsearse. En esta lección vamos a profundizar en ambos formatos desde la perspectiva de un ingeniero de datos: sus fortalezas reales, sus problemas ocultos y cuándo usar cada uno.

### CSV: el formato más simple y más traicionero del mundo

CSV (Comma-Separated Values) es probablemente el formato de datos más antiguo que sigue en uso masivo. Su idea es brutal en su simplicidad: valores separados por comas, un registro por línea. La primera línea (opcionalmente) contiene los nombres de las columnas. Y ya está. No hay tipos, no hay esquema, no hay metadatos. Un humano puede leer un CSV con los ojos. Excel lo abre. Cualquier lenguaje lo parsea en 3 líneas de código.

Pero esa misma simplicidad es su maldición. CSV no tiene estándar oficial riguroso (hay un RFC, el 4180, pero no todos lo siguen). No tiene tipos de datos — todo es texto. No indica si un "42" es el número cuarenta y dos o el código de departamento "42". No distingue entre nulo (dato ausente) y string vacío (dato presente pero sin contenido). Y tiene un enemigo mortal: la coma dentro del texto.

1# El CSV perfecto: funciona bien
2csv_perfecto = """nombre,ciudad,edad
3Ana López,Madrid,28
4Carlos Ruiz,Barcelona,35
5"""
6
7# El CSV del INFIERNO: problemas reales
8csv_infernal = """nombre,direccion,importe
9Ana López,"Calle Mayor, 5",29.99
10"Pedro ""El Grande""",Av. Diagonal 100,150.00
11María\nGarcía,Sin dirección,
12"""
13# Problemas:
14# 1. Comas dentro del campo → requiere comillas
15# 2. Comillas dentro del campo → requiere escape (doble comilla)
16# 3. Saltos de línea dentro del campo → rompe el parseo naive
17# 4. Campo vacío al final → ¿es None o string vacío?
18
19print("CSV parece simple... hasta que no lo es")

Los problemas ocultos de CSV: comas, comillas, saltos de línea y campos vacíos

### Los 7 pecados mortales del CSV

  1. 01.Sin tipos: "42" puede ser número, texto o código postal. Tú decides al leerlo.
  2. 02.Encoding variable: ¿UTF-8? ¿Latin-1? ¿Windows-1252? Los acentos se rompen si no aciertas.
  3. 03.Separador ambiguo: en España usamos coma decimal (3,14) — ¿y si el separador también es coma?
  4. 04.Sin esquema: no dice cuántas columnas hay. Una fila con 5 campos y otra con 6 = caos.
  5. 05.Nulos invisibles: un campo vacío (,,) puede ser "" o NULL. No hay forma de distinguirlo.
  6. 06.Sin metadatos: no sabes cuándo se creó, quién lo generó ni qué versión del esquema usa.
  7. 07.Tamaño: para leer la fila 1.000.000 tienes que recorrer las 999.999 anteriores.

Consejo de senior: cuando recibas un CSV de un cliente o proveedor, NUNCA asumas que está bien formado. Ábrelo con un editor de texto plano primero. Verifica el encoding (UTF-8 con BOM es una pesadilla), el separador real (a veces usan punto y coma o tabulador) y si las comillas están bien escapadas. Los primeros 5 minutos de inspección te ahorran 5 horas de debugging.

### JSON: el lenguaje universal de la web

JSON (JavaScript Object Notation) nació en el mundo web pero se expandió a todo el ecosistema de datos. Sus ventajas sobre CSV son claras: soporta tipos (números, strings, booleanos, null), permite anidamiento (objetos dentro de objetos), acepta arrays, y tiene un estándar bien definido. Cuando una API te devuelve datos, casi siempre será JSON.

1import json
2
3# JSON tiene tipos nativos
4datos = {
5 "nombre": "Ana López", # string
6 "edad": 28, # number (entero)
7 "saldo": 1523.45, # number (decimal)
8 "es_vip": True, # boolean (true/false)
9 "telefono": None, # null (ausencia de dato)
10 "direcciones": [ # array
11 {"tipo": "casa", "ciudad": "Madrid"},
12 {"tipo": "trabajo", "ciudad": "Toledo"}
13 ]
14}
15
16# Serializar (Python dict → JSON string)
17json_str = json.dumps(datos, ensure_ascii=False, indent=2)
18print(json_str)
19
20# Deserializar (JSON string → Python dict)
21recuperado = json.loads(json_str)
22print(f"\nTipo de edad: {type(recuperado['edad'])}") # <class 'int'>
23print(f"Tipo de es_vip: {type(recuperado['es_vip'])}") # <class 'bool'>

JSON preserva tipos: números, booleanos y null sobreviven la serialización

### JSON Lines: el formato para datos grandes

JSON clásico tiene un problema con archivos grandes: necesitas cargar TODO el archivo en memoria para parsearlo (porque es un único array/objeto). Si tienes 2 GB de datos, necesitas 2 GB de RAM solo para leerlo. La solución: JSON Lines (JSONL). Un objeto JSON por línea, sin array contenedor. Puedes leer línea a línea sin cargar todo en memoria.

1import json
2
3# --- JSON clásico (todo en un array) ---
4# Problema: necesitas cargar TODO en memoria
5json_clasico = '[{"id":1,"nombre":"Ana"},{"id":2,"nombre":"Pedro"}]'
6todos = json.loads(json_clasico) # Carga TODO de golpe
7
8# --- JSON Lines (un objeto por línea) ---
9# Ventaja: puedes leer línea a línea
10jsonl_contenido = """{"id":1,"nombre":"Ana"}
11{"id":2,"nombre":"Pedro"}
12{"id":3,"nombre":"Lucía"}
13"""
14
15# Leer línea a línea (eficiente en memoria)
16for linea in jsonl_contenido.strip().split("\n"):
17 registro = json.loads(linea)
18 print(f" Procesando: {registro['nombre']}")
19
20# En un archivo real:
21# with open('datos.jsonl') as f:
22# for linea in f:
23# registro = json.loads(linea)
24# procesar(registro)

JSON Lines: un objeto por línea, procesable sin cargar todo en memoria

### CSV vs JSON: cuándo usar cada uno

  • CSV cuando: los datos son tabulares simples, el destinatario es Excel/humano, necesitas máxima compatibilidad, no hay anidamiento.
  • JSON cuando: los datos tienen estructura variable, hay anidamiento, necesitas preservar tipos, es comunicación entre sistemas (APIs).
  • JSONL cuando: tienes muchos registros JSON y necesitas procesarlos de forma eficiente sin cargar todo en memoria.
  • Ninguno cuando: tienes millones de registros y necesitas rendimiento — ahí necesitas formatos binarios (lo veremos en la siguiente lección).

Error que he cometido (y que cometerás): generar un JSON de 5 GB para "luego procesarlo". JSON no está diseñado para volúmenes grandes. Es texto plano — ocupa mucho espacio, es lento de parsear y no permite leer columnas individuales. Para datos grandes, existen formatos especializados que veremos a continuación. CSV y JSON son para intercambio y volúmenes pequeños-medianos (hasta cientos de MB).

### Leer CSV con problemas reales en Python

1import csv
2
3# Escribir un CSV problemático para practicar
4with open('ventas_problemas.csv', 'w', newline='', encoding='utf-8') as f:
5 writer = csv.writer(f)
6 writer.writerow(['producto', 'descripcion', 'precio', 'stock'])
7 writer.writerow(['Camiseta', 'Algodón, talla M', '29.99', '150'])
8 writer.writerow(['Pantalón', 'Vaquero "slim fit"', '59.90', ''])
9 writer.writerow(['Chaqueta', 'Impermeable\nlarga', '120.00', '45'])
10
11# Leer manejando problemas
12with open('ventas_problemas.csv', 'r', encoding='utf-8') as f:
13 reader = csv.DictReader(f)
14 for fila in reader:
15 # Convertir precio a float (viene como texto)
16 precio = float(fila['precio'])
17 # Manejar stock vacío como 0
18 stock = int(fila['stock']) if fila['stock'] else 0
19 print(f" {fila['producto']:12} | {precio:>7.2f}€ | stock: {stock}")

CSV real: campos con comas, comillas y valores vacíos que hay que manejar

Truco profesional: siempre usa el módulo csv de Python para leer/escribir CSV. NUNCA hagas split(",") manual — falla con comas dentro de campos entrecomillados. El módulo csv maneja todos los edge cases del RFC 4180 automáticamente.

Consejo de senior: la primera vez que trabajé con datos de un cliente francés, mi pipeline explotaba con ValueError y no entendía por qué. El problema: en España y buena parte de Europa los precios vienen como "45,90" en vez de "45.90". Python no sabe que esa coma es un decimal — para él es un string raro. La solución rápida: float(valor.replace(",", ".")). Si trabajas con Pandas, el parámetro read_csv(decimal=",") lo resuelve de un plumazo. Te lo vas a encontrar más veces de las que imaginas.

### Dos herramientas que verás en los ejercicios

1import io
2
3# io.StringIO: hace que un texto se comporte como un fichero abierto.
4# Útil para probar sin crear archivos de verdad.
5texto = "a,b,c\n1,2,3"
6fichero_falso = io.StringIO(texto)
7print(fichero_falso.readline()) # "a,b,c\n"
8
9# set: un conjunto que no admite repetidos.
10usuarios = set()
11usuarios.add("ana")
12usuarios.add("ana") # esta no entra: ya estaba
13usuarios.add("pedro")
14print(len(usuarios)) # 2

io.StringIO para probar sin ficheros, y set para contar sin repetidos

### Resumen

  1. 01.CSV es simple pero traicionero: sin tipos, sin esquema, problemas de encoding y separadores.
  2. 02.JSON preserva tipos y permite anidamiento, pero es verboso y lento con archivos grandes.
  3. 03.JSONL (un objeto por línea) resuelve el problema de memoria de JSON.
  4. 04.Ambos son formatos de texto — legibles pero ineficientes para volúmenes grandes.
  5. 05.Usa csv.DictReader (nunca split manual) y json.loads/dumps para manejarlos correctamente.

## ejercicios

[01]

Leer un CSV con problemas de encoding

El proveedor "EuroFoods" te envía un CSV con separador punto y coma (;) y decimales con coma. Léelo correctamente y calcula el total de importes.

💡 Resultado esperado

Columnas detectadas: ['producto', 'precio', 'cantidad']

  Jamón ibérico        → 459.00€
  Queso manchego       → 312.50€
Cargando editor...
[02]

Convertir JSON anidado a CSV plano

Aplana productos con specs anidadas y escríbelos como CSV. Ojo: un 0 SÍ es un dato, no lo confundas con "no hay dato".

💡 Resultado esperado

nombre,precio,ram_gb,disco_gb,tag_principal
MacBook Pro,2499,16,512,portátil
iPad Air,699,8,256,tablet
AirPods Pro,279,,,audio
Cargando editor...
[03]

Procesar JSONL línea a línea

Un JSONL de eventos de e-commerce. Procesa línea a línea y calcula estadísticas. Ojo: una compra puede no traer importe.

💡 Resultado esperado

Total eventos: 10
Compras: 6
Facturación: 416.49€
Usuarios únicos: 5
Cargando editor...
[04]

Validador de CSV: detectar filas rotas

Un CSV tiene filas con problemas. La primera comprobación (número de columnas) ya está hecha. Añade las otras dos: nombre vacío y edad no numérica.

💡 Resultado esperado

Filas válidas: 2
Filas con error: 4
  Línea 3: ['columnas: esperadas 4, tiene 3']
  Línea 4: ['columnas: esperadas 4, tiene 5']
Cargando editor...

Regístrate para guardar tu progreso.

## comentarios

Reporta erratas, ayuda a otros o comparte tu opinión. Sé constructivo.

Inicia sesión para comentar y responder.

cargando comentarios...