Salida estructurada en Python
Metadatos de la lección
- Autoría:
- Ignacio Slater-Muñoz
- Última actualización:
- 28 de marzo de 2026
Encuentra el código de la lección:
Abstract
Si en la lección base PowerShell mostraba cómo producir objetos listos para consola y pipeline, aquí cambia el criterio de diseño de la salida estructurada. En Python, suele pensarse menos como algo que el shell presenta automáticamente y más como un dato explícito que luego otra parte del programa, un archivo o una API puede consumir.
Por eso esta comparación se apoya en dos movimientos frecuentes: serializar datos a JSON
para intercambiarlos con otras herramientas y usar
cuando esa información deja de ser ocasional y conviene modelarla como parte explícita
de la interfaz del programa.
@dataclass@dataclass
De Python a JSON
En la comparación con PowerShell aparece una diferencia importante de foco. En PowerShell, la salida estructurada suele pensarse para consumo inmediato: verla en consola, filtrarla, proyectarla o encadenarla en el pipeline. En Python, en cambio, muchas veces la estructura se prepara para consumo externo: guardarla en un archivo, enviarla por una API o compartirla con otro sistema.
JSON encaja bien en ese escenario porque ofrece un formato textual simple, portable y fácil de leer desde muchos lenguajes. Por eso es común construir una estructura en memoria y luego convertirla en una representación JSON estable.
from __future__ import annotations
import json
from pathlib import Path
from typing import TypeAlias
JSONValue: TypeAlias = (
dict[str, "JSONValue"]
| list["JSONValue"]
| str
| int
| float
| bool
| None
)
def save_to_json(data: JSONValue, filename: str | Path) -> None:
with Path(filename).open("w", encoding="utf-8") as file:
json.dump(data, file, indent=4, ensure_ascii=False, sort_keys=True) Detalles clave
-
permite dar nombre a un tipo complejo. En este caso,TypeAliasTypeAliasdescribe los valores que pueden aparecer en un JSON: objetos, listas, strings, números, booleanos yJSONValueJSONValue.NoneNone - El alias es recursivo porque un objeto JSON puede contener otros objetos JSON o listas de valores JSON dentro de sí. Por eso el tipo se define en términos de sí mismo en
ydict[str, "JSONValue"]dict[str, "JSONValue"](nota que el tipo va entre comillas ya que se refiere a un tipo que aún no está completamente definido a esas alturas).list["JSONValue"]list["JSONValue"] -
escribe la estructura directamente en un archivo abierto. No devuelve un string: toma el dato Python y lo serializa en el flujo de salida.json.dumpjson.dump - Las opciones elegidas controlan el formato del archivo:
agrega sangría para hacerlo legible,indent=4indent=4preserva caracteres no ASCII sin escapar innecesariamente, yensure_ascii=Falseensure_ascii=Falseordena las claves para obtener una salida más estable y fácil de comparar.sort_keys=Truesort_keys=True
De JSON a @dataclass@dataclass y viceversa
@dataclass@dataclass
En la lección base, marcaba el momento en
que la salida dejaba de ser solo un contenedor clave-valor y pasaba a tener una forma
más explícita.
[PSCustomObject][PSCustomObject]
Python no ofrece un equivalente directo con la misma integración de consola. Aun así,
suele marcar un cambio parecido: dejamos de usar un
contenedor genérico y pasamos a modelar una estructura con significado más claro y más
estable.
@dataclass@dataclass
Ese cambio se vuelve útil cuando la forma del dato deja de ser accidental: conviene nombrarla, acceder a sus campos como atributos y tratarla como parte de una interfaz que otras partes del programa pueden asumir y reutilizar.
from dataclasses import dataclass
import json
from pathlib import Path
from json_utils import JSONValue
@dataclass(frozen=True)
class Comic:
title: str
writer: str
release_year: int
def to_json_serializable(self) -> JSONValue:
return self.__dict__
def comic_from_json(file_path: str | Path) -> Comic:
with Path(file_path).open("r", encoding="utf-8") as file:
data = json.load(file)
return Comic(**data)
Además de volver más explícita la intención del modelo, genera automáticamente un constructor y una representación textual útil para
depuración. La diferencia importante no es solo cómo se imprime el valor, sino que ahora
la estructura tiene nombre y puede formar parte explícita de la interfaz del programa.
@dataclass@dataclass
Detalles clave
-
genera una clase pensada para representar datos. En este caso, deja explícito que un@dataclass@dataclasstiene título, escritor y año de publicación.ComicComic - Con
, la instancia se vuelve esencialmente inmutable: una vez creada, no deberías cambiar sus campos. Para este tipo de modelo eso suele ser útil porque reduce cambios accidentales y hace más claro su uso.frozen=Truefrozen=True - El método
devuelveto_json_serializableto_json_serializable, es decir, el diccionario de atributos de la instancia. Aquí funciona bien porque todos los campos ya son valores simples serializables a JSON. En un caso real, si hubiera fechas, enums u otros objetos más complejos, habría que convertirlos explícitamente antes de serializar.self.__dict__self.__dict__ -
hace la operación inversa ajson.loadjson.load: lee el contenido del archivo y lo convierte en una estructura Python, en este caso unjson.dumpjson.dump.dictdict - Luego,
usa unpacking de diccionario para mapear cada clave del JSON a un parámetro del constructor. Esto supone que el archivo tiene exactamente la forma esperada. Aquí no hay validación explícita: el ejemplo asume que el JSON ya viene bien formado y con las claves correctas.Comic(**data)Comic(**data)
Uso
comic.json:
{
"title": "Nightwing: Time of the Titans",
"writer": "Tom King",
"release_year": 2024
}comic = comic_from_json("comic.json")
print(comic)Conclusiones
La idea central de esta lección no es que Python replique el modelo de salida de PowerShell, sino que organiza la salida estructurada con otras prioridades. Mientras PowerShell destaca cuando quieres objetos listos para consola y pipeline, Python suele sobresalir cuando necesitas datos explícitos que luego persistes, intercambias o entregas a otras partes del programa.
En ese recorrido, JSON funciona como uno de los puentes más visibles: una estructura
en memoria puede guardarse en un archivo, viajar hacia otra herramienta y volver a
levantarse como dato dentro de Python. Cuando esa estructura deja de ser un
diccionario ocasional y pasa a tener una forma conocida, ayuda a nombrarla y tratarla como un modelo más claro y reusable.
@dataclass@dataclass
La lección propone, entonces, otro criterio de diseño para la salida estructurada: en Python, muchas veces importa más que el dato sea fácil de intercambiar, serializar y reutilizar que presentarlo automáticamente en terminal. La decisión importante no es solo cómo mostrar la salida, sino cuándo basta un contenedor flexible y cuándo conviene declarar un modelo explícito.
Puntos clave
- En Python, la salida estructurada suele pensarse menos para inspección inmediata en consola y más para persistencia, interoperabilidad e intercambio con otros sistemas.
- JSON ofrece un formato textual portable y estable para mover estructuras Python fuera del programa.
- Un
sirve bien como contenedor flexible, pero se vuelve un contrato débil cuando la forma del dato ya debería ser conocida y estable.dictdict -
permite nombrar esa estructura, acceder a sus campos como atributos y modelarla como parte explícita de la interfaz del programa.@dataclass@dataclass - Convertir entre JSON y un modelo explícito hace más claro qué datos espera y produce el programa, aunque en ejemplos simples todavía no haya validación formal.