Leer un archivo JSON y reconstruir el estado completo de una app en Python es clave para la persistencia. Aquí verás cómo implementar un método para cargar datos, instanciar objetos de Biblioteca, LibroFísico, Estudiante y Profesor, y validar campos como ISBN y disponibilidad usando with open, json.load, encoding y append.
¿Cómo cargar y reconstruir el estado de la aplicación desde JSON en Python?
La idea central es leer el archivo y recrear las instancias de tu dominio. En persistencia.py se crea un método para cargar datos que solo recibe self. Se abre el archivo con modo de solo lectura y el mismo encoding con el que se guardó. Luego, se usa json.load para obtener un diccionario con toda la información.
Abrir archivo con with open en modo "r" y mismo encoding.
Convertir el contenido a diccionario con json.load.
Usar las llaves del diccionario para reconstruir Biblioteca, libros y usuarios.
Retornar la instancia de Biblioteca para usarla en main.py.
¿Qué hace el método cargar_datos?
Define un nuevo método: no recibe parámetros extra.
Lee el archivo: usa "r" de read y el encoding correcto.
Carga el JSON a una variable datos: ahora es un diccionario con nombre, usuarios y libros.
Al final retorna la biblioteca creada.
¿Cómo leer el archivo con with open y json.load?
Usa el patrón de contexto y respeta el encoding al leer y escribir para evitar errores con caracteres especiales.
import json
classPersistencia:defcargar_datos(self):withopen(self.archivo,"r", encoding="utf-8")as f: datos = json.load(f)# a partir de aquí se reconstruyen las instancias# y se retorna la biblioteca
¿Cómo recrear libros y usuarios con clases y listas?
Primero se crea una instancia de Biblioteca. Luego se recorre la lista de libros del diccionario y se instancia LibroFísico con las llaves correctas: título, autor, ISBN y disponible. Finalmente, se agrega cada libro a la lista interna con append.
¿Cómo reconstruir libros con su disponibilidad?
Incluir disponibilidad es importante: al guardar, puede que un libro esté marcado como no disponible, y hay que preservarlo.
from biblioteca import Biblioteca
from libros import LibroFisico
biblioteca = Biblioteca(nombre=datos["nombre"])# ejemplo de uso de la llave "nombre"for dato_libro in datos["libros"]: libro = LibroFisico( titulo=dato_libro["titulo"], autor=dato_libro["autor"], isbn=dato_libro["isbn"], disponible=dato_libro["disponible"],) biblioteca.libros.append(libro)
¿Cómo diferenciar estudiantes y profesores al reconstruir usuarios?
En usuarios hay dos tipos: Estudiante y Profesor. La diferencia práctica en los datos es la presencia de la llave "carrera". Si existe, es Estudiante; si no, es Profesor.
from usuarios import Estudiante, Profesor
for dato_usuario in datos["usuarios"]:if"carrera"in dato_usuario: usuario = Estudiante( nombre=dato_usuario["nombre"], cedula=dato_usuario["cedula"], carrera=dato_usuario["carrera"],)else: usuario = Profesor( nombre=dato_usuario["nombre"], cedula=dato_usuario["cedula"],) biblioteca.usuarios.append(usuario)
Diferenciar por llave "carrera" simplifica el flujo.
Una buena práctica adicional sería guardar el tipo explícito en el JSON.
¿Cómo integrar la carga en main.py?
La biblioteca se define desde la función de carga y la guardada se mueve al final de la ejecución para persistir cualquier cambio.
Importar persistencia.
Asignar biblioteca desde cargar_datos.
Ejecutar la app.
Guardar después de los cambios.
¿Qué buenas prácticas y retos quedan pendientes?
El proyecto funciona, pero puede mejorar con manejo de errores y ajustes de persistencia.
Manejo de errores: ¿qué pasa si el archivo no existe al cargar?.
Consistencia: usar siempre el mismo encoding en lectura y escritura.
Limpieza: remover prints de depuración al finalizar.
Extensión de datos: incluir el tipo de usuario en el diccionario.
Verificación rápida: editar biblioteca.json y ver cambios reflejados al ejecutar python main.py.
Si ya lo implementaste, comparte en comentarios cómo resolviste el manejo de errores y qué mejoras sumarías a la persistencia.
Lectura de archivos JSON para reconstruir objetos en Python
Si llegaste hasta acá has culminado este curso y tienes las bases para empezar a implementar sistemas mejor estructurados con clases y objetos!
En mi pagina web comparto más información acerca de Python por si les interesa:
Siempre abierto a escucharlos!
Vamos al examen y espero que les vaya bien!
Comparto mi reto:
Cambios realizados
Archivo: : Añadí PersistenciaError, ArchivoNoEncontradoError y DatosInvalidosError para agrupar errores de I/O y formato.
Archivo: : Manejo de FileNotFoundError (inicia una biblioteca vacía), (lanza DatosInvalidosError), validación de estructura y captura de errores al guardar (lanza PersistenciaError). También omite entradas inválidas con aviso.
Archivo: : Envolví la carga en try/except para manejar errores de persistencia y, si falla, inicio una biblioteca vacía. Ahora el script termina si el usuario o libro no se encuentran y captura errores al guardar.
main.py
from exceptions import( LibroNoDisponibleError, UsuarioNoEncontradoError, PersistenciaError, DatosInvalidosError,)from persistencia import Persistencia
from biblioteca import Biblioteca
import sys
persistencia = Persistencia()try: biblioteca = persistencia.cargar_datos()except(PersistenciaError, DatosInvalidosError)as e:print(f"Error al cargar datos: {e}")print("Se iniciará una biblioteca vacía.") biblioteca = Biblioteca("Biblioteca Vacía")print("Bienvenido a Platzi Biblioteca")print("Libros disponibles:")for libro in biblioteca.libros_disponibles():print(libro.descripcion_completa)print()cedula =input("Digite el numero cedula: ")try: usuario = biblioteca.buscar_usuario(cedula)print(usuario.cedula, usuario.nombre)except UsuarioNoEncontradoError as e:print(e) sys.exit(1)titulo =input("Digite el titulo del libro: ")try: libro = biblioteca.buscar_libro(titulo)print(f"El libro que selecionaste es: {libro}")except LibroNoDisponibleError as e:print(e) sys.exit(1)resultado = usuario.solicitar_libro(libro.titulo)print(f"\n{resultado}")try: resultado_prestar = libro.prestar()print(f"\n{resultado_prestar}")except LibroNoDisponibleError as e:print(e)try: persistencia.guardar_datos(biblioteca)except PersistenciaError as e:print(f"No se pudo guardar la información: {e}")
persistencia.py
from exceptions import( LibroNoDisponibleError, UsuarioNoEncontradoError, PersistenciaError, DatosInvalidosError,)from persistencia import Persistencia
from biblioteca import Biblioteca
import sys
persistencia = Persistencia()try: biblioteca = persistencia.cargar_datos()except(PersistenciaError, DatosInvalidosError)as e:print(f"Error al cargar datos: {e}")print("Se iniciará una biblioteca vacía.") biblioteca = Biblioteca("Biblioteca Vacía")print("Bienvenido a Platzi Biblioteca")print("Libros disponibles:")for libro in biblioteca.libros_disponibles():print(libro.descripcion_completa)print()cedula =input("Digite el numero cedula: ")try: usuario = biblioteca.buscar_usuario(cedula)print(usuario.cedula, usuario.nombre)except UsuarioNoEncontradoError as e:print(e) sys.exit(1)titulo =input("Digite el titulo del libro: ")try: libro = biblioteca.buscar_libro(titulo)print(f"El libro que selecionaste es: {libro}")except LibroNoDisponibleError as e:print(e) sys.exit(1)resultado = usuario.solicitar_libro(libro.titulo)print(f"\n{resultado}")try: resultado_prestar = libro.prestar()print(f"\n{resultado_prestar}")except LibroNoDisponibleError as e:print(e)try: persistencia.guardar_datos(biblioteca)except PersistenciaError as e:print(f"No se pudo guardar la información: {e}")
exceptions.py
classBibliotecaError(Exception):"""Excepción base para errores de la biblioteca"""passclassLimitePrestamosError(BibliotecaError):"""Se excedió el límite de préstamos permitidos"""passclassTituloInvalidoError(BibliotecaError):"""El título del libro no es válido"""passclassLibroNoDisponibleError(BibliotecaError):"""El libro no está disponible para préstamo"""passclassUsuarioNoEncontradoError(BibliotecaError):"""El usuario no fue encontrado en el sistema"""passclassPersistenciaError(BibliotecaError):"""Errores relacionados con persistencia de datos (I/O, formato)"""passclassArchivoNoEncontradoError(PersistenciaError):"""El archivo de persistencia no fue encontrado"""passclassDatosInvalidosError(PersistenciaError):"""Los datos leídos tienen un formato inválido"""pass
Muchas gracias por el curso.
Estuvo excelente.
Hola a todos! 👋🏼
A continuación les dejo mi granito de arena al proyecto del curso, con tres modificaciones clave en las clases Libro, Estudiante, Profesor y Persistencia.
Guardado de usuarios por Estudiante y Profesor.
Definición de classmethods para recrear instancias de libros y usuarios.
Recrear biblioteca implementando los clasmethods e identificación de tipos de usuarios.
1️⃣ Identificar tipo de usuario enpersistencia.py
En en ejemplo que vimos en la clase, el profesor utiliza el atributo “carrera” para distinguir el tipo de usuario.
Pero, ¿Cómo distinguir entre un usuario de tipo Profesor y uno de tipo Estudiante sin utilizar alguno de los atributos de una de las clases?.
📌 Los objetos en Python tienen una propiedad especial llamada__class__ que nos permite acceder a la variable __name__ la cuál corresponde al nombre de de la clase a la que pertenece un objeto.
Al aplicarlo dentro del método de guardar_datos debemos cambiar el list comprehension donde iteramos a los usuarios:
defguardar_datos(self, biblioteca): datos ={"nombre": biblioteca.nombre,# Cada usuario estara contenido dentro de una llave con el nombre de su respectiva clase."usuarios":[{ usuario.__class__.__name__: usuario.__dict__ }for usuario in biblioteca.usuarios,"libros":[libro.__dict__ for libro in biblioteca.libros],"fecha_guardado": datetime.now().isoformat(),}withopen(self.archivo,"w", encoding="utf-8")as f: json.dump(datos, f, indent=4, ensure_ascii=False)
Al guardar el archivo JSON, obtendremos la siguiente estructura para usuarios:
2️⃣ Implementación del decorador@classmethodpara la reconstrucción de libros y usuarios.
En el ejemplo de la clase, se reconstruye la biblioteca creando cada uno de los libros y usuarios a partir de instancias directas de sus respectivas clases (LibroFisico, Estudiante y Profesor), sin embargo al hacerlo de esta forma estaríamos perdiendo información de cada instancia, por ejemplo:
Usuarios (Estudiante y Profesor): Libros que tenia prestados cada usuario.
Libros: Contador de las veces que ha sido prestado cada libro.
Este ejercicio es el ejemplo perfecto para implementar los conocimientos adquiridos en la clase #15. Implementaremos un @classmethod para recrear cada instancia sin perder información previa de cada objeto.
Libro
classLibro:def__init__(self, titulo, autor, isbn, disponible=True): self.titulo = titulo
self.autor = autor
self.isbn = isbn
self.disponible = disponible
self.__veces_prestado =0@classmethoddefrecrear(cls, titulo, autor, isbn, disponible, veces_prestado): libro = cls(titulo=titulo, autor=autor,isbn=isbn, disponible=disponible) libro.__veces_prestado = veces_prestado
return libro
# ...
recrear tiene como argumentos, veces_prestado, así cuando se crea la instancia de cls (Libro/LibroFisico, dependiendo de la clase que ejecute el método) también modifica el valor de dicho argumento, recreando un objeto con las mismas propiedades que habíamos guardado en el archivo biblioteca.json.
En las clases Estudiante y Profesor lo que cambia es el tributo carrera, por eso opté por definir el classmethod recrear en cada una de las clases que heredan de Usuario.
3️⃣ Recrear la biblioteca
Una vez hecha la modificación en la forma de guardar a los usuarios en el archivo biblioteca.json y haber definido un classmethod en cada una de las clases, es hora de implementarlos, el método cargar_datos quedaría de la siguiente forma:
classPersistencia:# ....defcargar_datos(self):withopen(self.archivo,"r", encoding="utf-8")as f: datos = json.load(f)# datos: {'titulo': 'Cien Años de Soledad', 'autor': 'Gabriel García Márquez', 'isbn': '9780307474728', 'disponible': True, '_Libro__veces_prestado': 0} biblioteca = Biblioteca(datos["nombre"])for dato_libro in datos["libros"]: libro = LibroFisico.recrear( titulo=dato_libro["titulo"], autor=dato_libro["autor"], isbn=dato_libro["isbn"], disponible=dato_libro["disponible"], veces_prestado=dato_libro["_Libro__veces_prestado"]) biblioteca.libros.append(libro)# datos: {'Profesor': {'nombre': 'Felipe', 'cedula': '123123123', 'libros_prestados': ['La Metamorfosis'], 'limite_libros': None}}# datos: {'Estudiante': {'nombre': 'Ana María López', 'cedula': '1001234567', 'libros_prestados': [], 'carrera': 'Ingeniería de Sistemas', 'limite_libros': 3}}for dato_usuario in datos["usuarios"]: tipo_usuario, info_usuario =list(dato_usuario.items())[0]if tipo_usuario =="Estudiante": usuario = Estudiante.recrear( nombre=info_usuario["nombre"], cedula=info_usuario["cedula"], carrera=info_usuario["carrera"], libros_prestados=info_usuario["libros_prestados"])else: usuario = Profesor.recrear( nombre=info_usuario["nombre"], cedula=info_usuario["cedula"], libros_prestados=info_usuario["libros_prestados"]) biblioteca.usuarios.append(usuario)return biblioteca
Espero este aporte les sea de utilidad, toda retroalimentación es bienvenida.
Acá el repo con el proyecto completo:
Made with 💜 by Paho.
Al cargar datos de JSON, se puede usar lógica condicional para inspeccionar las claves o valores dentro de cada diccionario, como la presencia de la clave carrera para diferenciar entre Estudiante y Profesor. Esto permite instanciar la clase correcta y reconstruir el objeto con sus atributos específicos, manteniendo la integridad del modelo de datos.
Otras mejoras realizadas en el proyecto:
Qué hice
Removí los prints de depuración en (mensajes de archivo faltante y advertencias de entradas inválidas).
Añadí el campo tipo al serializar usuarios en guardar_datos para poder restaurar correctamente Estudiante o Profesor.
Actualicé la carga (cargar_datos) para restaurar usuarios según tipo.
Verifiqué ejecución con python main.py.
Cambios clave (código):
(ya existente)
ENCODING = "utf-8"
(fragmentos modificados)
Serialización de usuarios (guardar_datos):
for usuario in biblioteca.usuarios:
udata = usuario.dict.copy()
udata["tipo"] = usuario.class.name
datos["usuarios"].append(udata)
Lectura silenciosa si no existe el archivo (sin prints):
Omitir entradas inválidas silenciosamente al cargar libros/usuarios:
except Exception:
continue
Restauración por tipo al cargar usuarios:
tipo = dato_usuario.get("tipo", "Profesor")
if tipo == "Estudiante":
usuario = Estudiante(
nombre=dato_usuario.get("nombre", ""),
cedula=dato_usuario.get("cedula", ""),
carrera=dato_usuario.get("carrera", ""),
)
else:
usuario = Profesor(
nombre=dato_usuario.get("nombre", ""), cedula=dato_usuario.get("cedula", "")
)
Verificación rápida
Edita (por ejemplo, cambia o añade un usuario, asegurándote de incluir la clave "tipo": "Estudiante" o "tipo": "Profesor" si creas entradas manualmente).
Ejecuta: python main.py
Los cambios en se leerán y reflejarán al iniciar (si el JSON es válido). Si el archivo no existe, el programa inicia una biblioteca vacía sin imprimir mensajes de depuración.
persistencia.py
import json
from datetime import datetime
from biblioteca import Biblioteca
from libros import LibroFisico
from usuarios import Estudiante, Profesor
from exceptions import( PersistenciaError, ArchivoNoEncontradoError, DatosInvalidosError,)from config import ENCODING
classPersistencia:def__init__(self, archivo="biblioteca.json")->None: self.archivo = archivo
defguardar_datos(self, biblioteca): datos ={"nombre": biblioteca.nombre,"usuarios":[],"libros":[libro.__dict__ for libro in biblioteca.libros],"fecha_guardado": datetime.now().isoformat(),}# Serializar usuarios incluyendo el tipo de usuario para restauraciónfor usuario in biblioteca.usuarios: udata = usuario.__dict__.copy() udata["tipo"]= usuario.__class__.__name__
datos["usuarios"].append(udata)try:withopen(self.archivo,"w", encoding=ENCODING)as f: json.dump(datos, f, indent=4, ensure_ascii=False)except Exception as e:raise PersistenciaError(f"Error guardando datos en {self.archivo}: {e}")defcargar_datos(self):try:withopen(self.archivo,"r", encoding=ENCODING)as f: datos = json.load(f)except FileNotFoundError:# Archivo ausente: devolver una biblioteca vacía sin imprimir mensajes de depuraciónreturn Biblioteca("Biblioteca Vacía")except json.JSONDecodeError as e:raise DatosInvalidosError(f"El archivo {self.archivo} contiene JSON inválido: {e}")except Exception as e:raise PersistenciaError(f"Error leyendo {self.archivo}: {e}")# validar estructura mínimaifnotisinstance(datos,dict)or"nombre"notin datos:raise DatosInvalidosError("Estructura de datos inválida en el archivo de persistencia") biblioteca = Biblioteca(datos.get("nombre","Biblioteca"))for dato_libro in datos.get("libros",[]):try: libro = LibroFisico( titulo=dato_libro.get("titulo",""), autor=dato_libro.get("autor",""), isbn=dato_libro.get("isbn",""), disponible=dato_libro.get("disponible",True),) biblioteca.libros.append(libro)except Exception:# Omitir entradas inválidas silenciosamentecontinuefor dato_usuario in datos.get("usuarios",[]):try: tipo = dato_usuario.get("tipo","Profesor")if tipo =="Estudiante": usuario = Estudiante( nombre=dato_usuario.get("nombre",""), cedula=dato_usuario.get("cedula",""), carrera=dato_usuario.get("carrera",""),)else: usuario = Profesor( nombre=dato_usuario.get("nombre",""), cedula=dato_usuario.get("cedula","")) biblioteca.usuarios.append(usuario)except Exception:# Omitir usuarios inválidos silenciosamentecontinuereturn biblioteca
Mejora de consistencia:
He centralizado el encoding en y actualicé para usar la constante ENCODING.
Cambios principales (códigos):
Nuevo archivo:
Contenido:
ENCODING = "utf-8"
Modificado:
Import añadido:
from config import ENCODING
Reemplazado:
with open(self.archivo, "w", encoding="utf-8") -> with open(self.archivo, "w", encoding=ENCODING)
with open(self.archivo, "r", encoding="utf-8") -> with open(self.archivo, "r", encoding=ENCODING)
Les comparto mi proyecto final:
muy buen curso
En el ultimo quiz, la pregunta Si quieres guardar la información de la biblioteca en un archivo JSON, ¿qué estructura usarías para almacenar libros y usuarios?, mo sería mejor un archivo biblioteca con una lista de diccionarios del tipo [{"titulo":"..", "autor":"..","isbn":".."}, {otro libro}, ... ] y otro igual para usuarios, enn vez de lo que dice la respuesta un diccionario con listas para usuarios y biblioteca. Pues cada usuario y libro, tienen varios atributos
Los dos casos son correctos, en el caso del diccionaro es para guardar un solo archivo con ambas keys (usuarios y libros)
Excelente, muchas gracias por este curso tan impactante, he aprendido muchos conocimientos...
Brutal este curso!
Muchas gracias!
Gracias por verlo!
Muchas gracias!!
Con gusto!
Excelente curso!
Gracias por verlo!
Hola, no pude terminar el examen porque en la pregunta relacionada a la clase manejo de excepciones, en donde dice 'Empareja los conceptos claves de manejo de excepciones en Python', al relacional ValueError con la deficion 'Tipo de error estándar para valores invalidos' no permite la relación entre estos y no me permite terminar el examen.
Al reconstruir algun usuario, ya sea profesor o estudiante como podemos tambien reconstruir la lista de prestamos o esa se pierde? (hice esta modificacion al for de persistencia, pero no se si sea correcta)
#'nombre':'alonso','id':221212,'prestamos':[],'carrera':'sis','limite_libros': 3for dato_usuario in datos["usuarios"]: usuario =Estudiante( nombre = dato_usuario['nombre'], id = dato_usuario['id'], carrera = dato_usuario['carrera']) usuario.prestamos= dato_usuario['prestamos'] usuario.limite_libros= dato_usuario['limite_libros'] biblioteca.usuarios.append(usuario)
Hola! Al realizar de esta manera la instancia no te marcó error? Ya que les estas pasando mas argumentos de los que están definidos en el costructor.
Lo que yo hice fue lo siguiente:
En el for, una vez instanciado el usuario, antes de agregarlo a la biblioteca le asignas los libros prestados
for user_data in data["users"]:if user_data.get("career"):user:User=Student( name=user_data.get("name"), id=user_data.get("id"), career=user_data.get("career"),)else: user =Professor(name=user_data.get("name"), id=user_data.get("id")) user.lended_books= user_data.get("lended_books") library.users.append(user)
Saludos! Espero haberme dado a entender
Bueno ya termine el tercer curso de python vamos al cuarto jaja. bueno a este proyecto para practicar y ampliarlo un poco mas queiro agregar una opcion de crear el libro y que genere un id automaticamente y le pida los datos ala persona que genero el libro asta hay jajaj y ver si eso lo puedo transladar a una base de datos como mongo db, voy a intentar eso con mi otro profesor que es chatGPT cualquier cosa le dejare aqui un comentario con mi repositorio despues de haberlo implemtado eso.
Buen proyecto personal! Si necesitas ayuda con algo me avisas.
En vez de ir directo a mongo puedes usar sqlite (con eso te ahorras algunas configuraciones) y luego si puedes hacer el cambio a mongo
🎯Cargar y Reconstruir Datos desde JSON en Python
🟦 1. META PRINCIPAL
🔍 "Leer un JSON, convertirlo en objetos y restaurar toda la app."
Reconstruyes:
🏛 Biblioteca
📚 Libros físicos
👩🎓 Estudiantes
👨🏫 Profesores
¿Para qué?
➡ Para que la app recupere su estado exacto cada vez que se ejecuta.
🟧 2. EL CAMINO COMPLETO
🚀 JSON → diccionario → instancias → Biblioteca lista
Pasos clave:
📥 Leer archivo (with open)
🔄 Convertir a diccionario (json.load)
🧱 Construir objetos según sus claves
🔁 Cargar libros y usuarios
🎁 Devolver la biblioteca reconstruida
🟩 3. EL MÉTODO cargar_datos
🧩 ¿Qué hace exactamente?
❗ Solo recibe self
📂 Abre archivo en "r" con encoding consistente
📦 Carga todo en un diccionario datos
🏗 Construye Biblioteca + Libros + Usuarios
🔙 Retorna una biblioteca completamente funcional
🟪 4. LECTURA DEL ARCHIVO JSON
📚 Cómo hacerlo bien
🧠 Reglas de oro:
Usa with open → evita errores y cierra solo
Mantén el mismo encoding de guardado
Usa json.load → convierte a diccionario lista para usar