Python JSON
Aprende a usar el módulo json de Python para codificar, decodificar, leer y escribir datos JSON, con ejemplos prácticos y manejo de errores.
El módulo integrado json de Python te permite convertir entre objetos Python y texto JSON con solo dos funciones en la mayoría de los casos. Este capítulo cubre todo lo que necesitas: mapeo de tipos de datos, dumps/loads para cadenas, dump/load para archivos, impresión con formato, manejo de errores y serialización personalizada.
¿Qué es JSON?
JSON (JavaScript Object Notation) es un formato de intercambio de datos ligero y basado en texto. Es legible por humanos, independiente del lenguaje y el formato predeterminado para la mayoría de las APIs web.
Un documento JSON está formado por dos estructuras:
- Objetos — colecciones desordenadas de pares clave/valor encerradas en
{}. Las claves siempre son cadenas entre comillas dobles. - Arrays — secuencias ordenadas de valores encerradas en
[].
Ejemplo de documento JSON:
{
"name": "John Doe",
"age": 30,
"city": "New York",
"hobbies": ["reading", "traveling", "photography"],
"active": true,
"score": null
}Mapeo de tipos Python–JSON
Al codificar o decodificar JSON, el módulo json de Python convierte los tipos según esta tabla:
| Tipo Python | Tipo JSON | Tipo Python (tras decodificar) |
|---|---|---|
dict | object {} | dict |
list, tuple | array [] | list |
str | string "" | str |
int, float | number | int o float |
True / False | true / false | bool |
None | null | None |
Ten en cuenta que las tuplas se convierten en arrays JSON y vuelven como listas Python tras la decodificación.
Codificar objetos Python a cadenas JSON
json.dumps() (dump-string) serializa un objeto Python en una cadena con formato JSON.
Codificación básica
Salida:
{"name": "John Doe", "age": 30, "city": "New York", "hobbies": ["reading", "traveling", "photography"]}Impresión con formato usando indent y sort_keys
Por defecto, json.dumps() produce una cadena compacta en una sola línea. Pasa indent para obtener una salida legible por humanos, y sort_keys=True para ordenar las claves del diccionario alfabéticamente:
import json
person = {
"name": "John Doe",
"age": 30,
"city": "New York",
"hobbies": ["reading", "traveling", "photography"]
}
print(json.dumps(person, indent=2, sort_keys=True))Salida:
{
"age": 30,
"city": "New York",
"hobbies": [
"reading",
"traveling",
"photography"
],
"name": "John Doe"
}Usa indent=2 o indent=4 al escribir archivos de configuración o depurar respuestas de API — facilita la lectura de estructuras anidadas.
Decodificar cadenas JSON a objetos Python
json.loads() (load-string) analiza una cadena JSON y devuelve el objeto Python equivalente.
Decodificación básica
Salida:
{'name': 'John Doe', 'age': 30, 'city': 'New York', 'hobbies': ['reading', 'traveling', 'photography']}
<class 'dict'>
John DoeEl valor decodificado es un diccionario Python normal, por lo que puedes acceder a sus claves con [] o .get(), iterarlo con for, etc.
Manejo de JSON mal formado
Si la entrada no es JSON válido, json.loads() lanza json.JSONDecodeError. Captúralo siempre al trabajar con datos de fuentes externas:
import json
raw = '{"name": "Alice", "age":}' # invalid — missing value
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"Invalid JSON: {e}")Salida:
Invalid JSON: Expecting value: line 1 column 24 (char 23)Consulta el capítulo Python try/except para una guía completa del manejo de excepciones.
Trabajar con JSON anidado
Los objetos JSON pueden contener otros objetos y arrays a cualquier profundidad. Accede a los valores anidados usando la notación [] encadenada:
Salida:
John
travelingPara estructuras muy anidadas, considera usar .get() con un valor predeterminado para evitar KeyError en claves ausentes:
city = person.get("address", {}).get("city", "unknown")El capítulo de diccionarios anidados cubre patrones para trabajar con dicts Python profundamente anidados.
Leer y escribir archivos JSON
json.dump() escribe en un objeto de archivo, y json.load() lee de uno. Son los equivalentes para archivos de dumps/loads.
Escribir JSON en un archivo
import json
data = {"name": "Alice", "scores": [95, 87, 92]}
with open("data.json", "w") as f:
json.dump(data, f, indent=2)Esto crea data.json con contenido formateado. Usar with open(...) garantiza que el archivo se cierre automáticamente — consulta manejo de archivos en Python para más detalles.
Leer JSON desde un archivo
import json
with open("data.json") as f:
data = json.load(f)
print(data)
print(data["scores"])Salida (asumiendo el archivo escrito anteriormente):
{'name': 'Alice', 'scores': [95, 87, 92]}
[95, 87, 92]Ejemplo de ciclo completo
import json
# Write
config = {"host": "localhost", "port": 5432, "debug": False}
with open("config.json", "w") as f:
json.dump(config, f, indent=2)
# Read back
with open("config.json") as f:
loaded = json.load(f)
print(loaded["port"]) # 5432
print(type(loaded["port"])) # <class 'int'>Obtener JSON desde una API web
En la mayoría de los proyectos decodificarás JSON que proviene de una respuesta HTTP. La popular biblioteca requests lo hace sencillo:
import requests
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
response.raise_for_status() # raises an error for 4xx/5xx responses
data = response.json() # equivalent to json.loads(response.text)
print(data["title"])
print(data["completed"])response.json() llama a json.loads() internamente. Llama siempre a raise_for_status() antes de analizar para que una solicitud fallida no devuelva silenciosamente un cuerpo de error.
Serialización personalizada con default
El módulo json no puede codificar objetos Python arbitrarios (como datetime.date) por defecto. Pasa un callable default o una subclase personalizada de JSONEncoder para manejarlos:
import json
import datetime
class DateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.date):
return obj.isoformat()
return super().default(obj)
event = {"name": "Conference", "date": datetime.date(2024, 1, 15)}
print(json.dumps(event, cls=DateEncoder))Salida:
{"name": "Conference", "date": "2024-01-15"}El objeto date se convierte a su cadena ISO 8601 antes de la serialización.
Deserialización personalizada con object_hook
object_hook se llama para cada objeto JSON (dict) al ser decodificado. Puedes usarlo para transformar datos al vuelo — por ejemplo, convirtiendo valores de cadena a los tipos Python correctos:
import json
def as_record(d):
"""Convert age field from string to int if present."""
if "age" in d:
d["age"] = int(d["age"])
return d
raw = '{"name": "Bob", "age": "25"}'
person = json.loads(raw, object_hook=as_record)
print(person)
print(type(person["age"])) # <class 'int'>Salida:
{'name': 'Bob', 'age': 25}
<class 'int'>Referencia rápida
| Función | Dirección | Origen/destino |
|---|---|---|
json.dumps(obj) | Python → JSON | devuelve un str |
json.loads(s) | JSON → Python | lee desde un str |
json.dump(obj, f) | Python → JSON | escribe en un archivo |
json.load(f) | JSON → Python | lee desde un archivo |
Parámetros opcionales clave:
indent=2— impresión con formato usando 2 espacios de sangríasort_keys=True— ordenar las claves del diccionario alfabéticamentecls=MyEncoder— usar una subclase personalizada deJSONEncoderobject_hook=fn— transformar cada object dict decodificado