Python y JSON forman una pareja natural.
Ya sea que estés construyendo una API REST con FastAPI
o Django,
procesando pipelines de datos, o simplemente leyendo un archivo de configuración, trabajarás con JSON constantemente.
La buena noticia: la biblioteca estándar de Python tiene todo lo que necesitas en el
módulo json.
Sin pip install requerido.
Las cuatro funciones que realmente usas
El módulo json te da cuatro funciones para el trabajo diario:
json.loads(str)— analiza una cadena JSON en un objeto Pythonjson.dumps(obj)— convierte un objeto Python en una cadena JSONjson.load(file)— analiza JSON directamente desde un objeto archivojson.dump(obj, file)— escribe un objeto Python como JSON en un archivo
La s en loads / dumps significa string (cadena).
Las que no tienen la s trabajan con objetos de archivo. Fácil de recordar una vez que conoces la regla.
json.loads() — Analizar una cadena JSON
import json
json_string = '{"name": "Alice", "age": 30, "active": true, "score": 98.5}'
user = json.loads(json_string)
print(user["name"]) # Alice
print(user["age"]) # 30
print(user["active"]) # True
print(type(user)) # <class 'dict'>Observa el mapeo de tipos: JSON true se convierte en Python True,
JSON false se convierte en Python False, JSON null se convierte en Python None.
Los objetos JSON se convierten en dict de Python, los arrays JSON se convierten en list de Python.
json.dumps() — Serializar a una cadena JSON
import json
user = {
"name": "Bob",
"age": 25,
"roles": ["admin", "editor"],
"active": True,
"extra": None
}
# Compacto (ideal para transmisión de red)
compact = json.dumps(user)
print(compact)
# {"name": "Bob", "age": 25, "roles": ["admin", "editor"], "active": true, "extra": null}
# Con formato (ideal para logs e inspección humana)
pretty = json.dumps(user, indent=2)
print(pretty)
# {
# "name": "Bob",
# "age": 25,
# "roles": [
# "admin",
# "editor"
# ],
# "active": true,
# "extra": null
# }Observa el mapeo de tipos inverso: Python True → JSON true,
Python None → JSON null. Python lo maneja automáticamente.
Leer JSON desde un archivo
Este es probablemente el caso de uso más común — leer un archivo de configuración o datos al arrancar:
import json
# Leer y analizar en un solo paso
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f)
print(config["database"]["host"]) # localhost
print(config["database"]["port"]) # 5432Siempre especifica encoding="utf-8" al abrir archivos JSON. JSON está especificado como UTF-8
por la RFC 8259,
y omitirlo puede causar problemas en Windows donde la codificación predeterminada es a veces cp1252.
Escribir JSON en un archivo
import json
results = {
"timestamp": "2024-01-15T09:30:00Z",
"total": 1523,
"processed": 1521,
"failed": 2,
"errors": [
{"id": 42, "reason": "missing field"},
{"id": 99, "reason": "invalid format"}
]
}
with open("results.json", "w", encoding="utf-8") as f:
json.dump(results, f, indent=2)
print("Resultados guardados en results.json")Manejar errores correctamente
json.loads() lanza
json.JSONDecodeError
(una subclase de ValueError) cuando la entrada no es JSON válido. Manéjala siempre al analizar
datos que no controlas:
import json
def safe_parse(json_str):
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
print(f"JSON inválido en línea {e.lineno}, columna {e.colno}: {e.msg}")
return None
data = safe_parse('{"name": "Alice"}') # funciona bien
bad = safe_parse('not json at all') # imprime el error, devuelve None
also_bad = safe_parse('{"key": }') # imprime el error con información de posiciónJSONDecodeError te da la línea y columna exactas donde falló el análisis,
lo cual es útil para depurar archivos JSON grandes.
Opciones útiles de dumps()
import json
data = {
"z_key": 1,
"a_key": 2,
"price": 9.999999999
}
# Ordenar claves alfabéticamente (genial para salida reproducible / diffs)
print(json.dumps(data, sort_keys=True, indent=2))
# {
# "a_key": 2,
# "price": 9.999999999,
# "z_key": 1
# }
# Asegurar que los caracteres no-ASCII se preserven (por defecto: escapados a \uXXXX)
data2 = {"city": "Münich", "greeting": "こんにちは"}
print(json.dumps(data2, ensure_ascii=False))
# {"city": "Münich", "greeting": "こんにちは"}
# Con ensure_ascii=True (por defecto):
print(json.dumps(data2))
# {"city": "M\u00fcnich", "greeting": "\u3053\u3093\u306b\u3061\u306f"}ensure_ascii=False es algo que siempre añado al escribir archivos JSON que
contienen texto no-ASCII. La versión escapada es JSON técnicamente válido pero mucho más difícil de leer en un editor de texto.
Serializar objetos personalizados
Por defecto, json.dumps() no puede serializar instancias de clases personalizadas ni objetos
datetime.
Tienes dos opciones: crear una subclase de
json.JSONEncoder,
o convertir a un dict primero:
import json
from datetime import datetime, date
# Opción 1: clase de codificador personalizada
class AppEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
return super().default(obj)
data = {"name": "Alice", "created_at": datetime(2024, 1, 15, 9, 30)}
print(json.dumps(data, cls=AppEncoder, indent=2))
# {
# "name": "Alice",
# "created_at": "2024-01-15T09:30:00"
# }
# Opción 2: parámetro default= (más simple para conversiones puntuales)
print(json.dumps(data, default=str, indent=2)) # convierte cualquier desconocido a strUn patrón práctico: Carga de archivo de configuración
Aquí hay un patrón del mundo real que uso en casi todos mis proyectos Python — un cargador de configuración que lee un archivo de configuración JSON con valores predeterminados razonables:
import json
import os
from pathlib import Path
DEFAULTS = {
"database": {"host": "localhost", "port": 5432},
"debug": False,
"log_level": "INFO"
}
def load_config(path="config.json"):
config = DEFAULTS.copy()
config_path = Path(path)
if config_path.exists():
with open(config_path, "r", encoding="utf-8") as f:
try:
user_config = json.load(f)
# Mezcla profunda: la configuración del usuario sobreescribe los valores por defecto
for key, value in user_config.items():
if isinstance(value, dict) and key in config:
config[key].update(value)
else:
config[key] = value
except json.JSONDecodeError as e:
print(f"Advertencia: config.json es inválido ({e.msg}), usando valores por defecto")
return config
config = load_config()
print(config["database"]["host"]) # localhost (o valor sobreescrito)Resumen
El módulo json de Python cubre todo lo que necesitas sin ninguna dependencia.
Las reglas clave: usa loads()/dumps() para cadenas, load()/dump()
para archivos, maneja siempre JSONDecodeError al analizar datos externos, y añade
ensure_ascii=False cuando tus datos contienen caracteres no-latinos.
Para depurar datos JSON, el Formateador JSON y el
Validador JSON pueden ahorrarte mucho tiempo.