Datos anidados como texto
JSON es el formato cotidiano de los archivos de configuración y de los datos que se envían entre programas y APIs web. Se compone de objetos {"clave": valor}, arreglos [1, 2], cadenas, números, true/false y null. Se corresponden directamente con Python: los objetos pasan a ser diccionarios, los arreglos listas, true/false pasan a True/False y null a None. A diferencia de una tabla CSV, JSON puede anidarse: una orden puede contener una lista de ejecuciones, cada una con sus propios campos.
import json
# Text to Python: loads() ("load string")
text = '{"symbol": "AAPL", "fills": [{"price": 184.25, "qty": 100}], "open": true, "note": null}'
order = json.loads(text)
print(order["fills"][0]["price"]) # 184.25
print(order["open"], order["note"]) # True None
# Python to text: dumps() ("dump string")
print(json.dumps({"symbol": "AAPL", "qty": 200})) # {"symbol": "AAPL", "qty": 200}Archivos: load y dump
Las versiones sin la s trabajan con archivos: json.load(file) lee y analiza un archivo abierto, y json.dump(data, file) lo escribe. Pasa indent=2 para que el archivo sea legible por personas y sort_keys=True para un orden de claves estable, lo que mantiene pequeñas las diferencias en el control de versiones. Abre siempre con with y encoding="utf-8".
import json
# Save a config, then load it back
config = {"fee": 0.001, "symbols": ["MSFT", "AAPL"], "limits": {"max_qty": 500}}
with open("config.json", "w", encoding="utf-8") as file:
json.dump(config, file, indent=2, sort_keys=True)
with open("config.json", encoding="utf-8") as file:
print(file.read())
# {
# "fee": 0.001,
# "limits": {
# "max_qty": 500
# },
# "symbols": [
# "MSFT",
# "AAPL"
# ]
# }
with open("config.json", encoding="utf-8") as file:
print(json.load(file) == config) # TrueLo que no sobrevive al viaje
JSON tiene pocos tipos, así que algunos valores de Python cambian por el camino. Las tuplas vuelven como listas, las claves de diccionario vuelven siempre como cadenas ({1: "a"} vuelve como {"1": "a"}), y los conjuntos, fechas y objetos personalizados no se pueden volcar (TypeError) hasta que los conviertas, por ejemplo una fecha con .isoformat() o un conjunto con sorted(...). Recuerda también que NaN e infinito no son JSON válido.
import json
from datetime import date
# Tuples become lists, int keys become strings
data = {"pair": (1, 2), 7: "seven"}
print(json.loads(json.dumps(data))) # {'pair': [1, 2], '7': 'seven'}
# A date has to be converted by hand
today = date(2025, 1, 2)
print(json.dumps({"day": today.isoformat()})) # {"day": "2025-01-02"}Entrada inválida
Un texto que llega del exterior, como un mensaje de un mercado, puede no ser JSON válido en absoluto. json.loads lanza entonces json.JSONDecodeError, una clase de ValueError, y un JSON válido aún puede carecer de la clave que quieres (KeyError) o tener otra forma (TypeError). Envuelve el análisis en try/except y decide qué significa un mensaje malo para tu programa. El próximo tema cubre las excepciones por completo.
import json
# Bad input raises, so catch it where you parse
def get_price(text):
try:
return json.loads(text)["price"]
except (ValueError, KeyError, TypeError):
return None
print(get_price('{"price": 184.25}')) # 184.25
print(get_price("not json")) # None
print(get_price('{"symbol": "AAPL"}')) # Nonejson.dumps o json.dump produzcan el texto, y que json.loads o json.load lo lean. Lo mismo vale al combinar ajustes: copia antes de cambiar, para que una configuración no se filtre en otra.json.loads(text)/json.dumps(data)trabajan con cadenas;json.load(file)/json.dump(data, file)con archivos.indent=2ysort_keys=Truehacen los archivos legibles y estables.- La entrada inválida lanza
ValueError,KeyErroroTypeError: captura lo que esperas.
A continuación: excepciones y manejo de errores, para que tus programas fallen con claridad y se recuperen cuando deban.
Lab: qué se califica
- Totalizar las órdenes de un archivo JSON con
order_total25 pts - Guardar ajustes como JSON legible con
save_config25 pts - Leer un mensaje de cotización con seguridad usando
parse_quote25 pts - Superponer ajustes sobre los valores por defecto con
merge_configs25 pts