orjson es una biblioteca JSON orientada a baja latencia y compatible de forma nativa con dataclasses, fechas y UUID. No conviene sustituir json automáticamente: el contrato cambia y el rendimiento debe medirse en el flujo completo.
La diferencia esencial: bytes
python -m pip install orjson
from datetime import datetime, timezone
import orjson
datos = {"evento": "acceso", "momento": datetime.now(timezone.utc)}
contenido: bytes = orjson.dumps(datos, option=orjson.OPT_UTC_Z)
recuperado = orjson.loads(contenido)
dumps() devuelve bytes, no str. Encaja con cuerpos HTTP y archivos binarios, pero puede romper código que concatena texto. Evita ciclos .decode() y .encode() innecesarios.
Tipos y opciones explícitas
Para tipos propios, entrega default y lanza TypeError cuando no exista conversión:
from decimal import Decimal
def default(obj):
if isinstance(obj, Decimal):
return str(obj)
raise TypeError
payload = orjson.dumps({"total": Decimal("19.90")}, default=default)
Convertir Decimal en texto es una decisión de la API. Documéntala y prueba el recorrido de ida y vuelta. La guía de JSON en Python explica los límites del formato y dataclasses presenta modelos que orjson serializa nativamente.
Migrar con seguridad
Crea pruebas con payloads reales: Unicode, fechas, enteros grandes, claves no textuales y valores no finitos. Compara corrección antes que velocidad. El benchmark debe incluir lectura, validación, serialización y transporte con datos representativos.
La documentación oficial de orjson, consultada el 22 de julio de 2026, describe tipos, opciones y diferencias de migración. Mantén json si cumple el requisito; adopta orjson cuando la medición justifique la dependencia y el contrato en bytes sea intencional.
Leer y escribir en el límite adecuado
orjson.loads() acepta bytes, bytearray, memoryview y texto UTF-8. Devuelve tipos JSON ordinarios y no reconstruye dataclasses, fechas o UUID automáticamente. Haz esa validación en una capa posterior.
from pathlib import Path
import orjson
def guardar_evento(ruta: Path, evento: dict) -> None:
ruta.write_bytes(orjson.dumps(evento))
def cargar_evento(ruta: Path) -> dict:
valor = orjson.loads(ruta.read_bytes())
if not isinstance(valor, dict):
raise ValueError("el documento debe ser un objeto JSON")
return valor
En HTTP, comprueba el contrato del framework. Algunas respuestas aceptan bytes; otras esperan un objeto Python y lo serializan. Serializar dos veces produce una cadena JSON con otro JSON escapado.
Fechas y política de zona horaria
orjson procesa datetime, date y time, pero la representación depende del valor y las opciones:
from datetime import datetime, timezone
import orjson
evento = {"creado": datetime(2026, 8, 21, 15, 0, tzinfo=timezone.utc)}
payload = orjson.dumps(evento, option=orjson.OPT_UTC_Z)
assert b"2026-08-21T15:00:00Z" in payload
OPT_NAIVE_UTC trata fechas ingenuas como UTC. Actívalo solo si el sistema garantiza ese significado, pues puede ocultar una hora local sin zona. OPT_OMIT_MICROSECONDS elimina precisión y también cambia el contrato.
Dataclasses, enums y UUID
El soporte nativo de dataclasses es cómodo, pero todos los campos serializables pueden aparecer. Un objeto interno no se convierte por eso en una API pública segura. Un nuevo campo operativo podría quedar expuesto.
Crea un diccionario explícito si necesitas nombres estables, versiones o exclusión de secretos. Enums y UUID tienen representaciones documentadas; conserva pruebas del formato esperado. default resuelve tipos no soportados y no intercepta universalmente los tipos nativos.
Claves y salida determinista
Las claves JSON son cadenas. OPT_NON_STR_KEYS convierte ciertas claves, pero dos claves Python diferentes podrían colisionar al convertirse. Prefiere normalización y validación explícitas.
OPT_SORT_KEYS genera un orden determinista para snapshots o comparación humana, con un coste adicional. El orden no debe expresar semántica. Firmas criptográficas y canonicalización exigen una especificación; ordenar claves no basta.
Enteros, floats e interoperabilidad
Los enteros Python tienen precisión arbitraria, pero muchos consumidores no. OPT_STRICT_INTEGER puede rechazar valores fuera del rango interoperable documentado. Decide si un identificador será número o cadena.
Valores no finitos como NaN e infinito no pertenecen al estándar JSON. No supongas que orjson replica la política de json. Inclúyelos en pruebas de migración y valida datos científicos.
Una entrada inválida lanza orjson.JSONDecodeError; una salida no soportada, JSONEncodeError. Captura errores solo donde puedas agregar contexto o convertirlos en una respuesta del protocolo. No ocultes la causa con except Exception.
Formato y logs
La salida compacta sirve para red y almacenamiento. OPT_INDENT_2 ayuda en archivos humanos y OPT_APPEND_NEWLINE en un documento por línea:
def linea_json(registro: dict) -> bytes:
return orjson.dumps(registro, option=orjson.OPT_APPEND_NEWLINE)
La velocidad no vuelve seguro registrar payloads completos. Elimina credenciales, datos personales y tokens. Aplica límites de tamaño para controlar memoria.
Benchmark representativo
Mide con la versión de Python, hardware, opciones y payloads reales. Calienta el código, repite varias veces y compara una mediana o distribución. Incluye lectura, escritura y conversiones:
from timeit import repeat
import json
import orjson
datos = [{"id": n, "activo": True, "nombre": f"item-{n}"} for n in range(1000)]
tiempos_json = repeat(lambda: json.dumps(datos), number=100, repeat=5)
tiempos_orjson = repeat(lambda: orjson.dumps(datos), number=100, repeat=5)
print(min(tiempos_json), min(tiempos_orjson))
No presentes esa relación como universal. Payload, CPU, versiones y opciones cambian el resultado. Si base de datos, red o validación dominan la latencia, la mejora puede ser irrelevante para el usuario.
Checklist de migración
- confirma si cada consumidor espera
strobytes; - compara Unicode, escapes, fechas, decimales, UUID y enums;
- prueba enteros grandes, floats no finitos y claves no textuales;
- verifica tipos de excepción usados por la aplicación;
- revisa integraciones con framework, caché, archivos y colas;
- conserva fixtures representativas consumidas por otros servicios;
- mide el recorrido completo;
- documenta opciones y política de compatibilidad.
Una función adaptadora pequeña puede centralizar opciones y errores. Así cada endpoint no inventa políticas distintas y es posible volver al módulo estándar.
Seguridad y compatibilidad operativa
Trata JSON como entrada no confiable. orjson analiza el documento, pero no impone schema, autorización ni límites de protocolo. Restringe el tamaño antes de cargarlo, valida profundidad y cantidad de elementos según el dominio y rechaza campos desconocidos cuando el contrato lo exija. La velocidad no elimina riesgos de denegación de servicio.
Al actualizar la dependencia, consulta las notas de versión y ejecuta fixtures de compatibilidad. Los wheels binarios y requisitos de plataforma difieren de un módulo estándar. Confirma su disponibilidad en build y producción, que debe reproducirse desde dependencias declaradas y no desde una compilación manual en el servidor.
Separa benchmarks de pruebas funcionales. El CI debe comprobar formato y errores de forma determinista; límites estrictos de tiempo fluctúan en runners compartidos. Sigue el rendimiento en un entorno controlado o usa umbrales amplios con contexto para investigar.
Haz comprensible cada opción durante la revisión. Una máscara con varias constantes puede estar en una función adaptadora con nombre de dominio y pruebas específicas. Quien mantiene el sistema debe entender por qué UTC usa Z, si se conservan microsegundos, cómo se exponen dataclasses y qué rangos numéricos se permiten.
Decidir la adopción
orjson es un buen candidato si el profiling demuestra un coste JSON relevante, la API de bytes encaja con el transporte y los tipos soportados coinciden con el contrato. El módulo json sigue siendo adecuado para payloads pequeños, scripts, máxima portabilidad o código que usa sus hooks de personalización.
Documenta la decisión y una medición de referencia. Revísala cuando cambien payloads o arquitectura. La serialización que importaba en una API local puede volverse insignificante detrás de base de datos o red. La dependencia debe justificar su lugar por valor medido y un contrato claro, no por un benchmark aislado.
Antes de desplegar, prueba además un payload real de cada consumidor importante. Conserva esas muestras sin datos sensibles y verifica tanto la ida como la vuelta cuando el sistema necesite round trip. Esta comprobación sencilla detecta cambios de nombres, precisión y tipos antes de que lleguen a otra aplicación.