Lucía compra dos tazas y una mochila en una tienda online. Un pedido, dos productos. Parece lo más simple del mundo hasta que tienes que guardarlo en un fichero, porque ahí te toca decidir si el pedido es una fila o varias, y la respuesta cambia según el formato que elijas. Vamos a contar el mismo pedido en CSV y en JSON, y a ver dónde se nota la diferencia.
## CSV: una tabla de texto plano
CSV significa "valores separados por comas" (comma-separated values). Es un fichero de texto donde cada línea es una fila y cada campo (cada dato suelto: el cliente, el precio…) va separado del siguiente por un carácter, normalmente una coma (las hojas de cálculo en España suelen usar punto y coma, que es lo mismo con otra letra). Es lo que sale al exportar una hoja de cálculo. Para guardar el pedido de Lucía, lo habitual es una fila por producto:
1pedido_id,cliente,producto,cantidad,precio21001,Lucía Ramos,Taza,2,8.531001,Lucía Ramos,Mochila,1,30.041002,Marcos Gil,Camiseta,1,15.0
CSV: el pedido 1001 ocupa dos filas. Añadimos un 1002 de Marcos para tener algo que contar.
Fíjate en el 1001 y en "Lucía Ramos": se repiten en las dos filas. No es un fallo, es el precio de la forma plana: el CSV solo sabe de filas y columnas, así que una lista de productos tiene que aplastarse de alguna manera. Hay dos salidas y ninguna es bonita.
La primera es la de arriba: repetir los datos del pedido en cada fila. La segunda es ensanchar la tabla con columnas `producto_1`, `producto_2`, `producto_3`… ¿Cuántas? Las que quepan en el pedido más grande que imagines, y el día que alguien compre un producto más, el esquema (la lista de columnas que decidiste que tiene la tabla) se rompe. Con las filas repetidas al menos el fichero crece sin cambiar de forma. Yo me quedo siempre con esa.
## JSON: cajas dentro de cajas
JSON (JavaScript Object Notation) es otro formato de texto, pero guarda los datos como pares nombre-valor (`"cliente": "Lucía Ramos"`) y permite que un valor sea a su vez una lista o un objeto (una caja con sus propios pares nombre-valor). A eso se le llama anidar: meter una estructura dentro de otra, como cajas dentro de una caja. Así queda el mismo pedido:
1[2 {3 "pedido_id": 1001,4 "cliente": "Lucía Ramos",5 "lineas": [6 {"producto": "Taza", "cantidad": 2, "precio": 8.5},7 {"producto": "Mochila", "cantidad": 1, "precio": 30.0}8 ]9 },10 {11 "pedido_id": 1002,12 "cliente": "Marcos Gil",13 "lineas": [14 {"producto": "Camiseta", "cantidad": 1, "precio": 15.0}15 ]16 }17]
JSON: el pedido aparece una sola vez y sus productos viven dentro de "lineas".
Aquí el pedido existe una sola vez, con sus productos colgando dentro, y cada pedido puede traer los que quiera. Por eso el JSON es el formato natural de las API: una API es la puerta por la que un programa le pide datos a otro (por ejemplo, una app del tiempo pidiéndole la previsión a un servidor). Y a los datos que llevan su estructura escrita dentro, pero sin la rigidez de una tabla, se les llama datos semiestructurados.
## Leer los dos y contar lo mismo
Python trae `csv` y `json` en su librería estándar (lo que ya viene instalado con Python). Leer un texto y convertirlo en estructuras que el programa pueda manejar se llama parsear (hacer el parseo). Este código parsea los dos textos y calcula pedidos, unidades y total en cada caso. Tres cosas para leerlo sin tropezar: `io.StringIO` simula un fichero con un texto, para no depender de archivos; las llaves con `for` (un conjunto) descartan los repetidos, y por eso cuentan pedidos y no filas; y el `sum` con dos `for` seguidos recorre cada pedido y, dentro de él, cada línea. Puedes copiarlo tal cual y ejecutarlo:
1import csv2import io3import json45texto_csv = """pedido_id,cliente,producto,cantidad,precio61001,Lucía Ramos,Taza,2,8.571001,Lucía Ramos,Mochila,1,30.081002,Marcos Gil,Camiseta,1,15.09"""1011texto_json = """12[13 {"pedido_id": 1001, "cliente": "Lucía Ramos",14 "lineas": [{"producto": "Taza", "cantidad": 2, "precio": 8.5},15 {"producto": "Mochila", "cantidad": 1, "precio": 30.0}]},16 {"pedido_id": 1002, "cliente": "Marcos Gil",17 "lineas": [{"producto": "Camiseta", "cantidad": 1, "precio": 15.0}]}18]19"""2021# CSV: cada fila es una línea de pedido; los pedidos hay que reconstruirlos22filas = list(csv.DictReader(io.StringIO(texto_csv)))23pedidos_csv = {f["pedido_id"] for f in filas}24unidades_csv = sum(int(f["cantidad"]) for f in filas)25total_csv = sum(int(f["cantidad"]) * float(f["precio"]) for f in filas)2627# JSON: cada elemento ya es un pedido, con su lista dentro28pedidos = json.loads(texto_json)29unidades_json = sum(linea["cantidad"] for p in pedidos for linea in p["lineas"])30total_json = sum(linea["cantidad"] * linea["precio"] for p in pedidos for linea in p["lineas"])3132print("CSV :", len(pedidos_csv), "pedidos,", unidades_csv, "unidades,", total_csv, "€")33print("JSON:", len(pedidos), "pedidos,", unidades_json, "unidades,", total_json, "€")
Salida: "CSV : 2 pedidos, 4 unidades, 62.0 €" y "JSON: 2 pedidos, 4 unidades, 62.0 €". Mismo resultado, distinto camino.
Mira las dos diferencias. En el CSV, para saber cuántos pedidos hay tienes que deducirlo: las filas son líneas, y los pedidos salen de agrupar por `pedido_id` (de ahí el conjunto). En el JSON, `len(pedidos)` y listo. Y en el CSV todo llega como texto: el tipo de dato (si un valor es texto o es número) no viene escrito, así que `"2"` no es un número hasta que haces `int(...)`. El JSON sí distingue números de textos.
Cuando leas un CSV, convierte cada columna a su tipo en cuanto lo cargas (`int`, `float`, fechas). Si dejas `"10"` y `"9"` como texto, al ordenarlos el 10 va antes que el 9, y es un bug que se descubre tarde. Esto lo vemos en las lecciones de Python de la plataforma.
## Cuándo uso cada uno
| Pregunta | CSV | JSON |
|---|---|---|
| Forma de los datos | Tabla plana | Estructura anidada |
| Lista de productos | Filas repetidas | Lista dentro |
| Tipos de dato | Todo texto | Varios tipos |
| Lo abre cualquiera | Sí, en una hoja | Mejor con código |
| Su sitio natural | Exportar tablas | APIs |
Mi regla: si los datos van a acabar en una tabla (un análisis, una base de datos, meter miles de filas de golpe), usa CSV, porque es lo que ya son. Si los datos tienen forma de árbol, con cosas dentro de cosas, o vienen de una API, usa JSON y no te pelees con él: aplastar un JSON anidado en un CSV para luego reconstruirlo es como desmontar una estantería para meterla en el coche y montarla otra vez en el salón. Se hace, pero solo cuando no queda otra.
El CSV no tiene estándar estricto de verdad: si el nombre de Lucía se guardara como "Ramos, Lucía", esa coma tendría que ir entre comillas, y si no, la fila del pedido tendría una columna de más. El módulo `csv` de Python ya gestiona las comillas; no cortes líneas con `split(",")` a mano.
En resumen: el CSV cuenta el pedido como filas de una tabla y repite lo que haga falta; el JSON lo cuenta como una caja con sus líneas dentro. Ninguno es mejor en abstracto, pero cada uno encaja con un tipo de dato. Para practicar, copia el código del post, cambia un dato (la cantidad de las tazas, por ejemplo) en los dos textos y comprueba que los dos totales siguen coincidiendo.