{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Modificado en 2019-1 al 2026-1 por Equipo Docente IIC2233. </font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Serialización de objetos](#Serialización-de-objetos)\n",
    "    1. [JSON](#JSON)\n",
    "        1. [Serialización de Objetos Personalizados](#serialización-de-objetos-personalizados)\n",
    "            1. [Personalizar la serialización en `json`](#personalizar-la-serialización-en-json)\n",
    "            2. [Personalizar la deserialización en `json`](#personalizar-la-deserialización-en-json)\n",
    "        2. [Errores en serialización](#errores-en-serialización)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Serialización de objetos\n",
    "\n",
    "Toda la información que almacena un computador se guarda en base a *bits* (ceros y unos) y *bytes* (secuencias de 8 *bits*). Esta semana tendremos un primer acercamiento al uso y manejo de *bytes* en Python.\n",
    "\n",
    "Imaginemos que buscamos guardar una estructura de datos o una instancia de una clase para volver a leerla más adelante o para comunicarla a otro programa. De alguna forma, estos datos deben ser guardados como una serie o secuencia de *bytes*. Aquí es cuando aparece el concepto de **serialización**.\n",
    "\n",
    "Este concepto se refiere al procedimiento de transformar un objeto en una secuencia o serie de *bytes*. Esto nos permite almacenar la información y el estado de un objeto de forma persistente, por ejemplo en un archivo o una base de datos para poder consultarlo más tarde. También nos permite enviar el objeto a otros computadores y programas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## JSON\n",
    "\n",
    "**JSON** (JavaScript Object Notation) es un formato de texto estándar de intercambio de datos que puede ser interpretado por muchos lenguajes, y por ende es algo ampliamente utilizado para traspasar información de un programa a otro (por ejemplo, la comunicación entre dos computadores mediante internet). Además, JSON es *human-readable*, es decir, puede ser fácilmente leído y entendido por humanos.\n",
    "\n",
    "**El formato en que almacena la información es similar, pero no igual, a los diccionarios de Python.**\n",
    "\n",
    "En JSON solo es posible serializar instancias de `int`, `str`, `float`, `dict`, `bool`, `list`, `tuple` y `None`, de acuerdo a esta tabla de transformación que puedes revisar en [este link](https://docs.python.org/3.12/library/json.html#encoders-and-decoders). Por defecto no es posible serializar funciones o instancias de otras clases.\n",
    "\n",
    "En Python, existe un módulo llamado `json` que provee métodos para serializar objetos en el formato JSON. Este módulo ofrece los siguientes métodos:\n",
    "\n",
    "- `dumps`: serializa un objeto, es decir, lo **guarda** como un `str` en formato JSON.\n",
    "- `loads`: deserializa un `str` en formato JSON, es decir, lo **carga** un objeto de Python.\n",
    "\n",
    "Una vez que un objeto es serializado, este es persistente y está listo para volver a ser usado en el futuro por el mismo u otro programa."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Primero, veamos cómo se comporta JSON al serializar y deserializar una lista con distintos valores:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Resultado serialización: [\"a\", 1, 3, \"hola\"]\n",
      "Tipo de versión serializada: <class 'str'>\n",
      "Resultado deserialización: ['a', 1, 3, 'hola']\n",
      "\n",
      "Lista original: ['a', 1, 3, 'hola']\n",
      "Lista deserializada: ['a', 1, 3, 'hola']\n",
      "¿Las listas son iguales? True\n",
      "¿Las listas son el mismo objeto? False\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "\n",
    "lista = [\"a\", 1, 3, \"hola\"]\n",
    "lista_serializada = json.dumps(lista)\n",
    "\n",
    "print(f\"Resultado serialización: {lista_serializada}\")\n",
    "print(f\"Tipo de versión serializada: {type(lista_serializada)}\")\n",
    "\n",
    "lista_deserializada = json.loads(lista_serializada)\n",
    "print(f\"Resultado deserialización: {lista_deserializada}\")\n",
    "\n",
    "print()\n",
    "print(f\"Lista original: {lista}\")\n",
    "print(f\"Lista deserializada: {lista_deserializada}\")\n",
    "print(f\"¿Las listas son iguales? {lista == lista_deserializada}\")\n",
    "print(f\"¿Las listas son el mismo objeto? {lista is lista_deserializada}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pero hay que tener ojo con la transformación de algunos tipos de datos, ya que -como se explica en la [tabla de transformación](https://docs.python.org/3.12/library/json.html#encoders-and-decoders)- algunos objetos de Python comparten un mismo tipo de dato en el formato JSON: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Resultado serialización: [null, 1, 22.33, {\"a\": true, \"b\": false}]\n",
      "Tipo de versión serializada: <class 'str'>\n",
      "Resultado deserialización: [None, 1, 22.33, {'a': True, 'b': False}]\n",
      "Tipo de versión deserializada: <class 'list'>\n",
      "\n",
      "Tupla original: (None, 1, 22.33, {'a': True, 'b': False})\n",
      "Tupla deserializada: [None, 1, 22.33, {'a': True, 'b': False}]\n",
      "¿Las tuplas son iguales? False\n",
      "¿Las tuplas son el mismo objeto? False\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "\n",
    "tupla = (None, 1, 22.33, {\"a\": True, \"b\": False})\n",
    "tupla_serializada = json.dumps(tupla)\n",
    "\n",
    "print(f\"Resultado serialización: {tupla_serializada}\")\n",
    "print(f\"Tipo de versión serializada: {type(tupla_serializada)}\")\n",
    "\n",
    "tupla_deserializada = json.loads(tupla_serializada)\n",
    "print(f\"Resultado deserialización: {tupla_deserializada}\")\n",
    "print(f\"Tipo de versión deserializada: {type(tupla_deserializada)}\")\n",
    "\n",
    "print()\n",
    "print(f\"Tupla original: {tupla}\")\n",
    "print(f\"Tupla deserializada: {tupla_deserializada}\")\n",
    "print(f\"¿Las tuplas son iguales? {tupla == tupla_deserializada}\")\n",
    "print(f\"¿Las tuplas son el mismo objeto? {tupla is tupla_deserializada}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Además, `json` nos ofrece los métodos `dump` y `load` (casi el mismo nombre que los anteriores, pero sin la **`s`** al final). Estos métodos también serializan y deserializan, pero **a través de archivos**: \n",
    "\n",
    "- `dump`: guarda un archivo con el objeto serializado.\n",
    "- `load`: deserializa un objeto almacenado en un archivo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lista original: [1, 2, 3, 7, 8, 3]\n",
      "Lista cargada : [1, 2, 3, 7, 8, 3]\n",
      "¿Las listas son iguales? True\n",
      "¿Las listas son el mismo objeto? False\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "from os import path\n",
    "\n",
    "\n",
    "lista = [1, 2, 3, 7, 8, 3]\n",
    "\n",
    "with open(path.join(\"data\", \"mi_lista.json\"), \"w\") as archivo:\n",
    "    json.dump(lista, archivo)\n",
    "\n",
    "with open(path.join(\"data\", \"mi_lista.json\"), \"r\") as archivo:\n",
    "    lista_cargada = json.load(archivo)\n",
    "\n",
    "print(f\"Lista original: {lista}\")\n",
    "print(f\"Lista cargada : {lista_cargada}\")\n",
    "print(f\"¿Las listas son iguales? {lista == lista_cargada}\")\n",
    "print(f\"¿Las listas son el mismo objeto? {lista is lista_cargada}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Serialización de Objetos Personalizados\n",
    "\n",
    "Dado que ahora entendemos un mejor, el la serialización y deserialización, puede que les surja la siguiente duda:\n",
    "> ¿Y si guardo mi información en clases personalizadas? ¿Puedo serializar la información de otros tipos de objetos?\n",
    "\n",
    "Como JSON solo permite serializar algunos tipos de objetos (`int`, `str`, `float`, `dict`, `bool`, `list`, `tuple` y `None`), si se quiere serializar la información de otros objetos, entonces habrá que adaptar la información para que sea compatible a lo que acepta JSON."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import count\n",
    "import json\n",
    "\n",
    "\n",
    "class Mascota:\n",
    "    id = count()\n",
    "\n",
    "    def __init__(self, nombre: str, edad: int, especie: str) -> None:\n",
    "        self.nombre = nombre\n",
    "        self.edad = edad\n",
    "        self.especie = especie\n",
    "        self.id_ = next(self.id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Datos en formato JSON: <class 'str'> {\"nombre\": \"Luna\", \"edad\": 8, \"especie\": \"Gato\", \"id_\": 0}\n",
      "Datos en formato Python: <class 'dict'> {'nombre': 'Luna', 'edad': 8, 'especie': 'Gato', 'id_': 0}\n"
     ]
    }
   ],
   "source": [
    "m = Mascota(\"Luna\", 8, \"Gato\")\n",
    "\n",
    "json_string = json.dumps(m.__dict__)\n",
    "print(\"Datos en formato JSON:\", type(json_string), json_string)\n",
    "\n",
    "json_deserializado = json.loads(json_string)\n",
    "print(\"Datos en formato Python:\", type(json_deserializado), json_deserializado)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como podemos ver, esto ayuda al momento de serializar la información de una clase, pero no nos permite cargar su información como una instancia de la misma. \n",
    "\n",
    "Para poder serializar y deserializar correctamente la información de objetos no compatibles con JSON será necesario personalizar la serialización y deserialización, para esto contamos con:\n",
    "- `json.JSONEncoder`\n",
    "- `object_hook`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Personalizar la serialización en `json`\n",
    "\n",
    "Cuando `json` trata de serializar un objeto, utiliza la clase `JSONEncoder` para transformar la información a un formato compatible con JSON. Esta clase es **extensible**, es decir, podemos crear clases que hereden de ella y así permitir que otros objetos sean serializables con `json`.\n",
    "\n",
    "Para lograr que nuestra clase que herede de `JSONEncoder` sea capaz de serializar la información, deberemos sobrescribir el método `default` y, como hicimos en el ejemplo anterior, transformar la información del objeto y llamar el método `default` por defecto con la información transformada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "from datetime import datetime\n",
    "\n",
    "\n",
    "class MascotaEncoder(json.JSONEncoder):\n",
    "\n",
    "    def default(self, obj):\n",
    "        \"\"\"Serializa en forma personalizada el objeto de tipo Mascota\"\"\"\n",
    "        if isinstance(obj, Mascota):\n",
    "            return {\n",
    "                \"Mascota_id\": obj.id_,\n",
    "                \"nombre\": obj.nombre,\n",
    "                \"edad\": obj.edad,\n",
    "                \"especie\": obj.especie,\n",
    "                \"ano_nacimiento\": datetime.now().year - obj.edad,\n",
    "            }\n",
    "        # Mantenemos la serialización por defecto para otros tipos\n",
    "        return super().default(obj)\n",
    "\n",
    "\n",
    "# Creamos tres instancias\n",
    "m1 = Mascota(\"Pepa\", 70, \"Tortuga\")\n",
    "m2 = Mascota(\"Cachirulo\", 5, \"Gato\")\n",
    "m3 = Mascota(\"Guillermo\", 0, \"Gato\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Probamos la serialización usando el método por defecto:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"nombre\": \"Pepa\", \"edad\": 70, \"especie\": \"Tortuga\", \"id_\": 1}\n"
     ]
    }
   ],
   "source": [
    "json_string = json.dumps(m1.__dict__)\n",
    "\n",
    "print(json_string)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora, comparemos el resultado al serializar usando nuestro método personalizado:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"Mascota_id\": 1, \"nombre\": \"Pepa\", \"edad\": 70, \"especie\": \"Tortuga\", \"ano_nacimiento\": 1956}\n",
      "{\"Mascota_id\": 2, \"nombre\": \"Cachirulo\", \"edad\": 5, \"especie\": \"Gato\", \"ano_nacimiento\": 2021}\n",
      "{\"Mascota_id\": 3, \"nombre\": \"Guillermo\", \"edad\": 0, \"especie\": \"Gato\", \"ano_nacimiento\": 2026}\n"
     ]
    }
   ],
   "source": [
    "json_string = json.dumps(m1, cls=MascotaEncoder)\n",
    "print(json_string)\n",
    "\n",
    "json_string = json.dumps(m2, cls=MascotaEncoder)\n",
    "print(json_string)\n",
    "\n",
    "json_string = json.dumps(m3, cls=MascotaEncoder)\n",
    "print(json_string)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También podemos extender el mismo `JSONEncoder` para que maneje **más de una clase personalizada**. Basta con agregar más ramas `isinstance` dentro del método `default`. Si el objeto no corresponde a ningún tipo conocido, la llamada a `super().default(obj)` levantará un `TypeError`, avisándonos que ese tipo no es serializable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Veterinario:\n",
      "{\"tipo\": \"Veterinario\", \"nombre\": \"Dra. Torres\", \"especialidad\": \"Ex\\u00f3ticos\"}\n",
      "\n",
      "Mascota:\n",
      "{\"tipo\": \"Mascota\", \"nombre\": \"Pepa\", \"edad\": 70, \"especie\": \"Tortuga\"}\n",
      "\n",
      "Lista mixta:\n",
      "[\n",
      "  {\n",
      "    \"tipo\": \"Mascota\",\n",
      "    \"nombre\": \"Cachirulo\",\n",
      "    \"edad\": 5,\n",
      "    \"especie\": \"Gato\"\n",
      "  },\n",
      "  {\n",
      "    \"tipo\": \"Veterinario\",\n",
      "    \"nombre\": \"Dra. Torres\",\n",
      "    \"especialidad\": \"Ex\\u00f3ticos\"\n",
      "  },\n",
      "  {\n",
      "    \"tipo\": \"Mascota\",\n",
      "    \"nombre\": \"Guillermo\",\n",
      "    \"edad\": 0,\n",
      "    \"especie\": \"Gato\"\n",
      "  }\n",
      "]\n",
      "\n",
      "Tipo no soportado → Object of type object is not JSON serializable\n"
     ]
    }
   ],
   "source": [
    "class Veterinario:\n",
    "    def __init__(self, nombre: str, especialidad: str) -> None:\n",
    "        self.nombre = nombre\n",
    "        self.especialidad = especialidad\n",
    "\n",
    "\n",
    "class MascotaVeterinarioEncoder(json.JSONEncoder):\n",
    "\n",
    "    def default(self, obj):\n",
    "        if isinstance(obj, Mascota):\n",
    "            return {\n",
    "                \"tipo\": \"Mascota\",\n",
    "                \"nombre\": obj.nombre,\n",
    "                \"edad\": obj.edad,\n",
    "                \"especie\": obj.especie,\n",
    "            }\n",
    "        if isinstance(obj, Veterinario):\n",
    "            return {\n",
    "                \"tipo\": \"Veterinario\",\n",
    "                \"nombre\": obj.nombre,\n",
    "                \"especialidad\": obj.especialidad,\n",
    "            }\n",
    "        # Para cualquier otro tipo no soportado, levanta TypeError\n",
    "        return super().default(obj)\n",
    "\n",
    "\n",
    "v = Veterinario(\"Dra. Torres\", \"Exóticos\")\n",
    "\n",
    "# Serializar un Veterinario\n",
    "print(\"Veterinario:\")\n",
    "print(json.dumps(v, cls=MascotaVeterinarioEncoder))\n",
    "\n",
    "# Serializar una Mascota con el mismo encoder\n",
    "print(\"\\nMascota:\")\n",
    "print(json.dumps(m1, cls=MascotaVeterinarioEncoder))\n",
    "\n",
    "# Serializar una lista mixta\n",
    "print(\"\\nLista mixta:\")\n",
    "print(json.dumps([m2, v, m3], cls=MascotaVeterinarioEncoder, indent=2))\n",
    "\n",
    "# Intentar serializar un tipo no soportado → TypeError\n",
    "try:\n",
    "    json.dumps(object(), cls=MascotaVeterinarioEncoder)\n",
    "except TypeError as e:\n",
    "    print(f\"\\nTipo no soportado → {e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Personalizar la deserialización en `json`\n",
    "\n",
    "De forma análoga, podemos personalizar la **deserialización**. Para esto debemos implementar una función debe ser capaz de manejar un diccionario y retorne un objeto de Python. Esta función se entregará al parámetros `object_hook` de los métodos `load` y `loads`. En el proceso de deserialización, todo objeto JSON será convertido a un diccionario de Python, y luego pasado a la función `object_hook` para hacer la transformación final. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por ejemplo, si queremos cargar datos JSON en una lista de tuplas en vez de un diccionario:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('nombre', 'Luna'), ('edad', 8), ('especie', 'Gato'), ('ano_nacimiento', 2017)]\n"
     ]
    }
   ],
   "source": [
    "def funcion_lista_hook(diccionario: dict):\n",
    "    return [(key, value) for key, value in diccionario.items()]\n",
    "\n",
    "\n",
    "json_string = '{\"nombre\": \"Luna\", \"edad\": 8, \"especie\": \"Gato\", \"ano_nacimiento\": 2017}'\n",
    "datos = json.loads(json_string, object_hook=funcion_lista_hook)\n",
    "\n",
    "print(datos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "O si queremos volver a instanciar las Mascotas de antes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[<__main__.Mascota object at 0x735294db1550>, <__main__.Mascota object at 0x735294db16d0>, <__main__.Mascota object at 0x735294db0d10>, <__main__.Mascota object at 0x735294db1850>]\n"
     ]
    }
   ],
   "source": [
    "def funcion_mascota_hook(diccionario: dict) -> Mascota:\n",
    "    mascota = Mascota(diccionario[\"nombre\"], diccionario[\"edad\"], diccionario[\"especie\"])\n",
    "    mascota.id_ = diccionario[\"Mascota_id\"]\n",
    "    return mascota\n",
    "\n",
    "\n",
    "lista_json_mascotas = [\n",
    "    '{\"Mascota_id\": 0, \"nombre\": \"Luna\", \"edad\": 8, \"especie\": \"Gato\", \"ano_nacimiento\": 2017}',\n",
    "    '{\"Mascota_id\": 1, \"nombre\": \"Pepa\", \"edad\": 70, \"especie\": \"Tortuga\", \"ano_nacimiento\": 1955}',\n",
    "    '{\"Mascota_id\": 2, \"nombre\": \"Cachirulo\", \"edad\": 5, \"especie\": \"Gato\", \"ano_nacimiento\": 2020}',\n",
    "    '{\"Mascota_id\": 3, \"nombre\": \"Guillermo\", \"edad\": 0, \"especie\": \"Gato\", \"ano_nacimiento\": 2025}',\n",
    "]\n",
    "\n",
    "lista_objetos_mascotas = []\n",
    "for json_mascota in lista_json_mascotas:\n",
    "    objeto_mascota = json.loads(json_mascota, object_hook=funcion_mascota_hook)\n",
    "    lista_objetos_mascotas.append(objeto_mascota)\n",
    "\n",
    "print(lista_objetos_mascotas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es importante recordar que **todo objeto de JSON es convertido a un diccionario**, y luego entregado al `object_hook`. Esto tiene implicancias cuando tenemos un JSON con objetos anidados:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Me llegó el diccionario: {}\n",
      "Lo transformaré en 42\n",
      "\n",
      "Me llegó el diccionario: {'f': True}\n",
      "Lo transformaré en 42\n",
      "\n",
      "Me llegó el diccionario: {'b': 1, 'c': [2, 3, 42], 'd': None, 'e': 42}\n",
      "Lo transformaré en 33\n",
      "\n",
      "Me llegó el diccionario: {'a': 33}\n",
      "Lo transformaré en 42\n",
      "\n",
      "42\n"
     ]
    }
   ],
   "source": [
    "def funcion_lista_hook(diccionario):\n",
    "    \"\"\"Esta función transforma un diccionario en un número\"\"\"\n",
    "    print(f\"Me llegó el diccionario: {diccionario}\")\n",
    "    valor = 33 if len(diccionario) > 1 else 42\n",
    "    print(f\"Lo transformaré en {valor}\\n\")\n",
    "    return valor\n",
    "\n",
    "\n",
    "json_string = '{\"a\": {\"b\": 1, \"c\": [2, 3, {}], \"d\": null, \"e\": {\"f\": true}}}'\n",
    "datos = json.loads(json_string, object_hook=funcion_lista_hook)\n",
    "\n",
    "print(datos)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué pasó ahí arriba? Lo primero que podemos notar es que el proceso de conversión funciona de adentro hacia afuera, y además que el resultado que entregue `object_hook` se \"arrastra\" en el proceso.\n",
    "\n",
    "También se puede ver que las las listas y otros tipos de datos no pasan por el `object_hook`, sino que la función sólo se llama cuando se pasa por un diccionario. Y esta es una de las principales diferencias con cuando se usa `cls`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Errores en serialización\n",
    "\n",
    "Ocasionalmente puede ocurrir que te aparezcan errores cuando deserializas, esto puede ser porque no se pasaron todos los datos o se corrompió la información."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "ename": "JSONDecodeError",
     "evalue": "Expecting ',' delimiter: line 1 column 29 (char 28)",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mJSONDecodeError\u001b[39m                           Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[15]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# Falta el ']' del final\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[43mjson\u001b[49m\u001b[43m.\u001b[49m\u001b[43mloads\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43m[\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mArreglo que no es cerrado\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/lib/python3.12/json/__init__.py:346\u001b[39m, in \u001b[36mloads\u001b[39m\u001b[34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[39m\n\u001b[32m    341\u001b[39m     s = s.decode(detect_encoding(s), \u001b[33m'\u001b[39m\u001b[33msurrogatepass\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m    343\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m    344\u001b[39m         parse_int \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m parse_float \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m    345\u001b[39m         parse_constant \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_pairs_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m kw):\n\u001b[32m--> \u001b[39m\u001b[32m346\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_default_decoder\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    347\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m    348\u001b[39m     \u001b[38;5;28mcls\u001b[39m = JSONDecoder\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/lib/python3.12/json/decoder.py:337\u001b[39m, in \u001b[36mJSONDecoder.decode\u001b[39m\u001b[34m(self, s, _w)\u001b[39m\n\u001b[32m    332\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mdecode\u001b[39m(\u001b[38;5;28mself\u001b[39m, s, _w=WHITESPACE.match):\n\u001b[32m    333\u001b[39m \u001b[38;5;250m    \u001b[39m\u001b[33;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[32m    334\u001b[39m \u001b[33;03m    containing a JSON document).\u001b[39;00m\n\u001b[32m    335\u001b[39m \n\u001b[32m    336\u001b[39m \u001b[33;03m    \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m337\u001b[39m     obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m=\u001b[49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    338\u001b[39m     end = _w(s, end).end()\n\u001b[32m    339\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m end != \u001b[38;5;28mlen\u001b[39m(s):\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/lib/python3.12/json/decoder.py:353\u001b[39m, in \u001b[36mJSONDecoder.raw_decode\u001b[39m\u001b[34m(self, s, idx)\u001b[39m\n\u001b[32m    344\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Decode a JSON document from ``s`` (a ``str`` beginning with\u001b[39;00m\n\u001b[32m    345\u001b[39m \u001b[33;03ma JSON document) and return a 2-tuple of the Python\u001b[39;00m\n\u001b[32m    346\u001b[39m \u001b[33;03mrepresentation and the index in ``s`` where the document ended.\u001b[39;00m\n\u001b[32m   (...)\u001b[39m\u001b[32m    350\u001b[39m \n\u001b[32m    351\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m    352\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m353\u001b[39m     obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mscan_once\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    354\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m    355\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m JSONDecodeError(\u001b[33m\"\u001b[39m\u001b[33mExpecting value\u001b[39m\u001b[33m\"\u001b[39m, s, err.value) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n",
      "\u001b[31mJSONDecodeError\u001b[39m: Expecting ',' delimiter: line 1 column 29 (char 28)"
     ]
    }
   ],
   "source": [
    "# Falta el ']' del final\n",
    "json.loads('[\"Arreglo que no es cerrado\"')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "JSONDecodeError",
     "evalue": "Expecting ':' delimiter: line 1 column 51 (char 50)",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mJSONDecodeError\u001b[39m                           Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# Falta un ':' entre \"mascota_mas_nueva\" y \"Guillermo\"\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[43mjson\u001b[49m\u001b[43m.\u001b[49m\u001b[43mloads\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43m{\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmascota_mas_vieja\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m: \u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mPepa\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m, \u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mmascota_mas_nueva\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m \u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mGuillermo\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m}\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m \n",
      "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.9/lib/python3.12/json/__init__.py:346\u001b[39m, in \u001b[36mloads\u001b[39m\u001b[34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[39m\n\u001b[32m    341\u001b[39m     s = s.decode(detect_encoding(s), \u001b[33m'\u001b[39m\u001b[33msurrogatepass\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m    343\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m    344\u001b[39m         parse_int \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m parse_float \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m    345\u001b[39m         parse_constant \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_pairs_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m kw):\n\u001b[32m--> \u001b[39m\u001b[32m346\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_default_decoder\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    347\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m    348\u001b[39m     \u001b[38;5;28mcls\u001b[39m = JSONDecoder\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.9/lib/python3.12/json/decoder.py:338\u001b[39m, in \u001b[36mJSONDecoder.decode\u001b[39m\u001b[34m(self, s, _w)\u001b[39m\n\u001b[32m    333\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mdecode\u001b[39m(\u001b[38;5;28mself\u001b[39m, s, _w=WHITESPACE.match):\n\u001b[32m    334\u001b[39m \u001b[38;5;250m    \u001b[39m\u001b[33;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[32m    335\u001b[39m \u001b[33;03m    containing a JSON document).\u001b[39;00m\n\u001b[32m    336\u001b[39m \n\u001b[32m    337\u001b[39m \u001b[33;03m    \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m338\u001b[39m     obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m=\u001b[49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    339\u001b[39m     end = _w(s, end).end()\n\u001b[32m    340\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m end != \u001b[38;5;28mlen\u001b[39m(s):\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.9/lib/python3.12/json/decoder.py:354\u001b[39m, in \u001b[36mJSONDecoder.raw_decode\u001b[39m\u001b[34m(self, s, idx)\u001b[39m\n\u001b[32m    345\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Decode a JSON document from ``s`` (a ``str`` beginning with\u001b[39;00m\n\u001b[32m    346\u001b[39m \u001b[33;03ma JSON document) and return a 2-tuple of the Python\u001b[39;00m\n\u001b[32m    347\u001b[39m \u001b[33;03mrepresentation and the index in ``s`` where the document ended.\u001b[39;00m\n\u001b[32m   (...)\u001b[39m\u001b[32m    351\u001b[39m \n\u001b[32m    352\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m    353\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m354\u001b[39m     obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mscan_once\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    355\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m    356\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m JSONDecodeError(\u001b[33m\"\u001b[39m\u001b[33mExpecting value\u001b[39m\u001b[33m\"\u001b[39m, s, err.value) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n",
      "\u001b[31mJSONDecodeError\u001b[39m: Expecting ':' delimiter: line 1 column 51 (char 50)"
     ]
    }
   ],
   "source": [
    "# Falta un ':' entre \"mascota_mas_nueva\" y \"Guillermo\"\n",
    "json.loads('{\"mascota_mas_vieja\": \"Pepa\", \"mascota_mas_nueva\" \"Guillermo\"}') "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "ename": "JSONDecodeError",
     "evalue": "Expecting ',' delimiter: line 1 column 22 (char 21)",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mJSONDecodeError\u001b[39m                           Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# Falta una ',' entre los strings del arreglo\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[43mjson\u001b[49m\u001b[43m.\u001b[49m\u001b[43mloads\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m'\u001b[39;49m\u001b[33;43m[\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mLuna\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m, \u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mCachirulo\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m \u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mGuillermo\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m]\u001b[39;49m\u001b[33;43m'\u001b[39;49m\u001b[43m)\u001b[49m \n",
      "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.9/lib/python3.12/json/__init__.py:346\u001b[39m, in \u001b[36mloads\u001b[39m\u001b[34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[39m\n\u001b[32m    341\u001b[39m     s = s.decode(detect_encoding(s), \u001b[33m'\u001b[39m\u001b[33msurrogatepass\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m    343\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m    344\u001b[39m         parse_int \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m parse_float \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[32m    345\u001b[39m         parse_constant \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_pairs_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m kw):\n\u001b[32m--> \u001b[39m\u001b[32m346\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_default_decoder\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    347\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m    348\u001b[39m     \u001b[38;5;28mcls\u001b[39m = JSONDecoder\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.9/lib/python3.12/json/decoder.py:338\u001b[39m, in \u001b[36mJSONDecoder.decode\u001b[39m\u001b[34m(self, s, _w)\u001b[39m\n\u001b[32m    333\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mdecode\u001b[39m(\u001b[38;5;28mself\u001b[39m, s, _w=WHITESPACE.match):\n\u001b[32m    334\u001b[39m \u001b[38;5;250m    \u001b[39m\u001b[33;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[32m    335\u001b[39m \u001b[33;03m    containing a JSON document).\u001b[39;00m\n\u001b[32m    336\u001b[39m \n\u001b[32m    337\u001b[39m \u001b[33;03m    \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m338\u001b[39m     obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m=\u001b[49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    339\u001b[39m     end = _w(s, end).end()\n\u001b[32m    340\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m end != \u001b[38;5;28mlen\u001b[39m(s):\n",
      "\u001b[36mFile \u001b[39m\u001b[32m~/.pyenv/versions/3.12.9/lib/python3.12/json/decoder.py:354\u001b[39m, in \u001b[36mJSONDecoder.raw_decode\u001b[39m\u001b[34m(self, s, idx)\u001b[39m\n\u001b[32m    345\u001b[39m \u001b[38;5;250m\u001b[39m\u001b[33;03m\"\"\"Decode a JSON document from ``s`` (a ``str`` beginning with\u001b[39;00m\n\u001b[32m    346\u001b[39m \u001b[33;03ma JSON document) and return a 2-tuple of the Python\u001b[39;00m\n\u001b[32m    347\u001b[39m \u001b[33;03mrepresentation and the index in ``s`` where the document ended.\u001b[39;00m\n\u001b[32m   (...)\u001b[39m\u001b[32m    351\u001b[39m \n\u001b[32m    352\u001b[39m \u001b[33;03m\"\"\"\u001b[39;00m\n\u001b[32m    353\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m354\u001b[39m     obj, end = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mscan_once\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    355\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m    356\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m JSONDecodeError(\u001b[33m\"\u001b[39m\u001b[33mExpecting value\u001b[39m\u001b[33m\"\u001b[39m, s, err.value) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m\n",
      "\u001b[31mJSONDecodeError\u001b[39m: Expecting ',' delimiter: line 1 column 22 (char 21)"
     ]
    }
   ],
   "source": [
    "# Falta una ',' entre los strings del arreglo\n",
    "json.loads('[\"Luna\", \"Cachirulo\" \"Guillermo\"]') "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estos son solo ejemplos de leer incorrectamente un archivo o texto, lo importante es recordar el nombre de los errores asociados -en este caso `JSONDecodeError`- para así poder identificar de dónde podría estar proviniendo el error."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
