JSON en Python: json.load, json.dump, opciones y errores comunes

El módulo json de la biblioteca estándar de Python convierte entre objetos Python y JSON de forma directa. Está en todos los proyectos que consumen APIs, guardan configuración o intercambian datos entre servicios, así que conocer sus opciones y puntos de fallo habituales vale la pena.

json.load y json.loads

import json

# json.load: lee de un fichero
with open("datos.json", encoding="utf-8") as f:
    datos = json.load(f)

# json.loads: parsea una cadena
texto = '{"nombre": "Ana", "edad": 28, "activo": true}'
datos = json.loads(texto)
print(datos)           # {'nombre': 'Ana', 'edad': 28, 'activo': True}
print(type(datos))     # 

# true/false/null de JSON ? True/False/None de Python
datos2 = json.loads('{"valor": null, "ok": false}')
print(datos2)  # {'valor': None, 'ok': False}

json.dump y json.dumps

import json

config = {
    "host": "localhost",
    "puerto": 5432,
    "ssl": True,
    "timeout": None
}

# json.dumps: serializa a cadena
texto = json.dumps(config)
print(texto)
# {"host": "localhost", "puerto": 5432, "ssl": true, "timeout": null}

# Con opciones de formato
bonito = json.dumps(config, indent=2, ensure_ascii=False, sort_keys=True)
print(bonito)

# json.dump: escribe directamente a fichero
with open("config.json", "w", encoding="utf-8") as f:
    json.dump(config, f, indent=2, ensure_ascii=False)

ensure_ascii=False: imprescindible para español

datos = {"mensaje": "Bienvenido, ¿cómo estás?", "ciudad": "Almería"}

# Por defecto: escapa los caracteres no-ASCII
print(json.dumps(datos))
# {"mensaje": "Bienvenido, ¿cómo estás?", "ciudad": "Almería"}

# ensure_ascii=False: caracteres reales
print(json.dumps(datos, ensure_ascii=False))
# {"mensaje": "Bienvenido, ¿cómo estás?", "ciudad": "Almería"}

JSONDecodeError: manejar JSON inválido

import json

textos = ['{"ok": true}', 'no es json', '{"clave": ']

for texto in textos:
    try:
        datos = json.loads(texto)
        print(f"OK: {datos}")
    except json.JSONDecodeError as e:
        print(f"JSON inválido: {e.msg} en posición {e.pos}")

Serializar objetos propios: el parámetro default

import json
from datetime import datetime, date
from dataclasses import dataclass, asdict

@dataclass
class Producto:
    nombre: str
    precio: float
    creado: date

def serializar(obj):
    if isinstance(obj, (date, datetime)):
        return obj.isoformat()
    if hasattr(obj, '__dict__'):
        return obj.__dict__
    raise TypeError(f"No serializable: {type(obj)}")

p = Producto("Teclado", 89.99, date(2026, 6, 1))
print(json.dumps(p, default=serializar, ensure_ascii=False))
# {"nombre": "Teclado", "precio": 89.99, "creado": "2026-06-01"}

# Alternativa con dataclass: asdict() convierte a dict automáticamente
print(json.dumps(asdict(p), default=serializar))

Parsear respuestas de API

import urllib.request
import json

def obtener_usuario(user_id):
    url = f"https://jsonplaceholder.typicode.com/users/{user_id}"
    try:
        with urllib.request.urlopen(url) as resp:
            datos = json.loads(resp.read().decode("utf-8"))
        return datos
    except (urllib.error.URLError, json.JSONDecodeError) as e:
        print(f"Error: {e}")
        return None

usuario = obtener_usuario(1)
if usuario:
    print(usuario.get("name"), usuario.get("email"))

Los parámetros más importantes de dumps() en la práctica son indent=2 para ficheros legibles y ensure_ascii=False para texto en español. Para objetos propios, el parámetro default es más limpio que implementar un JSONEncoder personalizado en la mayoría de los casos.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP