El módulo json viene con Python y hace lo evidente. Lo que merece la pena saber son los dos argumentos que casi nadie pone y los tipos que se niega a convertir.
También te puede interesar
Lo básico
import json
texto = json.dumps(datos, ensure_ascii=False, indent=2) # objeto -> texto
vuelta = json.loads(texto) # texto -> objeto
with p.open("w", encoding="utf-8") as f: json.dump(datos, f) # directo a archivo
with p.open(encoding="utf-8") as f: datos = json.load(f) # y desde archivo
Los cuatro nombres se confunden siempre: los que llevan una s trabajan con cadenas (string), los que no, con archivos.
Los dos argumentos que hay que poner
sin y con ensure_ascii=False{"ciudad": "A Coru\u00f1a"}
{"ciudad": "A Coruña"}
ensure_ascii=False deja los acentos como están. JSON es UTF-8 por definición, así que escaparlos solo sirve para hacer el archivo ilegible. Y indent=2 si lo va a leer una persona; sin él sale todo en una línea.
| Python | JSON |
|---|---|
dict |
objeto |
list, tuple |
array — las tuplas vuelven como listas |
str |
string |
int, float |
number |
True / False |
true / false |
None |
null |
Esa fila de las tuplas importa: una tupla sobrevive a la ida pero no a la vuelta. Lo mismo pasa con las claves numéricas de un diccionario, que vuelven como cadenas.
Lo que no sabe convertir
salidadate -> Object of type date is not JSON serializable Decimal -> Object of type Decimal is not JSON serializable set -> Object of type set is not JSON serializable bytes -> Object of type bytes is not JSON serializable
Se resuelve con default=, una función a la que json recurre cuando no sabe qué hacer:
def convertir(o):
if isinstance(o, (date, datetime)): return o.isoformat()
if isinstance(o, Decimal): return str(o)
if isinstance(o, set): return sorted(o)
raise TypeError(f"no sé convertir {type(o).__name__}")
json.dumps(datos, default=convertir, ensure_ascii=False)
salida{"fecha": "2026-10-07", "precio": "1.5", "tags": ["a", "b"]}
Decimal va como cadena a propósito. Convertirlo a float reintroduce el error de coma flotante que estabas evitando al usar Decimal. Con texto, al leerlo haces Decimal(valor) y recuperas el número exacto.Con dataclasses
from dataclasses import dataclass, asdict
@dataclass
class Producto:
nombre: str
precio: float
json.dumps(asdict(Producto("gorra", 12.5))) # al JSON
Producto(**json.loads(crudo)) # y de vuelta
salida{"nombre": "gorra", "precio": 12.5}
Producto(nombre='gorra', precio=12.5)
Ese Producto(**...) funciona mientras las claves coincidan con los campos. Si el JSON trae campos de más, revienta; si quieres algo que aguante datos ajenos, lo habitual es Pydantic, que además valida los tipos.
Cuando el JSON está roto
salidaJSONDecodeError: Illegal trailing comma before end of object (línea 1, columna 8)
JSONDecodeError hereda de ValueError y trae .msg, .lineno, .colno y .pos. Con un archivo grande, esos números son la diferencia entre arreglarlo en un minuto y buscar a ojo.
Y una advertencia práctica: una coma de más al final es inválida en JSON, aunque Python la acepte en sus propios diccionarios y casi todos los editores la pinten en verde. Tampoco se admiten comentarios.
Todo el código se ejecutó con Python 3.14 en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.