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.
