JSON es uno de los formatos más utilizados para intercambiar datos entre aplicaciones. APIs, servicios web, archivos de configuración y muchas herramientas almacenan información como objetos, listas, números, textos, booleanos y valores nulos. Python incluye el módulo json en su biblioteca estándar, por lo que puedes trabajar con este formato sin instalar paquetes adicionales.
Para aprovechar esta guía, repasa los diccionarios, las listas, los strings, el manejo de excepciones y la guía de Requests. Consulta también la documentación oficial del módulo json y la especificación RFC 8259.
Qué es JSON
JSON significa JavaScript Object Notation, pero se utiliza con muchos lenguajes. Un documento puede verse así:
{
"nombre": "Ana",
"edad": 30,
"activo": true,
"roles": ["editor", "autor"],
"telefono": null
}
Las claves deben estar entre comillas dobles. Los valores booleanos son true y false, y la ausencia se representa con null.
Equivalencias con Python
- Objeto JSON →
dict - Array JSON →
list - String JSON →
str - Número entero →
int - Número decimal →
float true/false→True/Falsenull→None
Convertir un string JSON a Python
import json
texto = '''
{
"nombre": "Ana",
"edad": 30,
"activo": true
}
'''
datos = json.loads(texto)
print(datos["nombre"])
print(type(datos))
loads significa “load string”. Devuelve estructuras de Python.
Convertir Python a JSON
usuario = {
"nombre": "Luis",
"edad": 25,
"activo": True,
"telefono": None,
}
texto_json = json.dumps(usuario)
print(texto_json)
dumps produce un string. Observa que True se convierte en true y None en null.
Crear JSON legible
texto_json = json.dumps(
usuario,
ensure_ascii=False,
indent=2,
)
indent añade sangría. ensure_ascii=False conserva caracteres como á, ñ y ç en vez de escribir secuencias escapadas. La codificación del archivo seguirá siendo importante.
Guardar JSON en un archivo
from pathlib import Path
import json
ruta = Path("usuario.json")
with ruta.open("w", encoding="utf-8") as archivo:
json.dump(
usuario,
archivo,
ensure_ascii=False,
indent=2,
)
dump escribe directamente en un archivo abierto. Especificar UTF-8 evita depender de la configuración regional.
Leer un archivo JSON
with ruta.open("r", encoding="utf-8") as archivo:
datos = json.load(archivo)
print(datos.get("nombre"))
load trabaja con un archivo; loads, con un string. La misma diferencia se aplica a dump y dumps.
Manejar errores de sintaxis
try:
datos = json.loads('{"nombre": "Ana",}')
except json.JSONDecodeError as error:
print(f"JSON inválido en línea {error.lineno}, columna {error.colno}")
JSON no permite comas finales, comentarios, claves sin comillas ni comillas simples como delimitadores estándar.
Manejar archivos ausentes
try:
with Path("config.json").open("r", encoding="utf-8") as archivo:
config = json.load(archivo)
except FileNotFoundError:
config = {}
except json.JSONDecodeError as error:
raise RuntimeError(f"Configuración inválida: {error}") from error
No conviene tratar un archivo corrupto como si estuviera vacío sin avisar. La estrategia depende de la importancia del dato.
Validar la estructura
Que un documento sea JSON válido no significa que tenga los campos correctos:
def validar_usuario(datos):
if not isinstance(datos, dict):
raise ValueError("Se esperaba un objeto")
nombre = datos.get("nombre")
edad = datos.get("edad")
if not isinstance(nombre, str) or not nombre.strip():
raise ValueError("Nombre inválido")
if not isinstance(edad, int) or edad < 0:
raise ValueError("Edad inválida")
Para proyectos grandes puedes usar JSON Schema, dataclasses o bibliotecas de validación, pero las reglas deben seguir siendo explícitas.
Listas de objetos
productos = [
{"id": 1, "nombre": "Teclado", "precio": 50.0},
{"id": 2, "nombre": "Ratón", "precio": 20.0},
]
with Path("productos.json").open("w", encoding="utf-8") as archivo:
json.dump(productos, archivo, ensure_ascii=False, indent=2)
Al leerlos, confirma que el nivel principal sea una lista y que cada elemento sea un diccionario.
Ordenar claves
print(json.dumps(usuario, indent=2, sort_keys=True))
sort_keys puede ser útil en tests, comparaciones y archivos generados automáticamente. No debe confundirse con el orden semántico de una interfaz.
Tipos que JSON no soporta directamente
JSON no conoce fechas, sets, bytes, rutas ni objetos personalizados:
from datetime import datetime
registro = {
"fecha": datetime.now(),
}
json.dumps(registro) producirá TypeError. Convierte explícitamente:
registro["fecha"] = registro["fecha"].isoformat()
Al leer, puedes reconstruir la fecha con datetime.fromisoformat.
Usar default con cuidado
from pathlib import Path
import json
def convertir(objeto):
if isinstance(objeto, Path):
return str(objeto)
raise TypeError(f"Tipo no serializable: {type(objeto).__name__}")
texto = json.dumps({"ruta": Path("datos.txt")}, default=convertir)
No conviertas silenciosamente cualquier objeto con str, porque puedes perder información y ocultar errores.
Decimales y precisión
Por defecto, los números decimales se convierten en float. Para datos financieros puedes usar Decimal al leer:
from decimal import Decimal
texto = '{"precio": 19.90}'
datos = json.loads(texto, parse_float=Decimal)
Para escribir un Decimal debes definir cómo representarlo, por ejemplo como string, y documentar esa decisión.
JSON de una API
import requests
respuesta = requests.get(
"https://api.example.com/productos",
timeout=10,
)
respuesta.raise_for_status()
datos = respuesta.json()
respuesta.json() puede fallar si el servidor devuelve HTML o contenido inválido. Valida también el encabezado y la estructura.
No confundir JSON con un diccionario
Un diccionario es un objeto en memoria. JSON es texto:
datos = {"activo": True} # dict
texto = '{"activo": true}' # str con JSON
No uses eval para convertir JSON. Además de ser incorrecto, puede ejecutar código peligroso. Usa siempre json.loads.
Escritura segura
Si el archivo es importante, escribe primero en una ruta temporal y reemplaza el original:
ruta = Path("config.json")
temporal = ruta.with_suffix(".tmp")
temporal.write_text(
json.dumps(config, ensure_ascii=False, indent=2),
encoding="utf-8",
)
temporal.replace(ruta)
Esto reduce el riesgo de dejar un archivo parcialmente escrito.
JSON Lines
Para registros grandes puede utilizarse un objeto JSON por línea:
with Path("eventos.jsonl").open("a", encoding="utf-8") as archivo:
evento = {"tipo": "login", "usuario": 10}
archivo.write(json.dumps(evento, ensure_ascii=False) + "\n")
JSON Lines permite procesar registros uno por uno sin cargar todo el archivo, aunque no es un único documento JSON convencional.
Seguridad
No guardes contraseñas, tokens o claves secretas en JSON dentro del repositorio. No confíes en datos recibidos de usuarios. Limita tamaños, valida tipos y evita usar campos JSON directamente para construir consultas SQL, rutas o comandos.
Pruebas
def test_conversion_json():
original = {"nombre": "Ana", "roles": ["editor"]}
texto = json.dumps(original, ensure_ascii=False)
recuperado = json.loads(texto)
assert recuperado == original
Añade casos con Unicode, valores nulos, listas vacías, JSON inválido, campos ausentes y números grandes.
Errores frecuentes
Los problemas habituales son usar comillas simples en el documento, dejar una coma final, confundir load con loads, no especificar UTF-8, asumir que todos los campos existen y serializar tipos no compatibles. También es común guardar datos ya formateados en lugar de valores estructurados.
Conclusión
El módulo json permite convertir entre texto JSON y estructuras de Python, leer y escribir archivos y procesar respuestas de APIs. Utiliza load/dump para archivos y loads/dumps para strings. Valida siempre la estructura, maneja errores, protege secretos y define cómo representar fechas, decimales y objetos especiales.






