Python

Python

Tutorial Python: Módulo json

Aprende a usar el módulo json en Python para serializar, deserializar y manejar estructuras JSON anidadas con ejemplos prácticos y personalización avanzada.

Aprende Python y certifícate

Serialización y deserialización

La serialización es el proceso de convertir estructuras de datos de Python en un formato que puede ser fácilmente almacenado o transmitido, mientras que la deserialización es el proceso inverso. El módulo json de Python proporciona funciones para realizar estas operaciones con el formato JSON (JavaScript Object Notation), un estándar ligero de intercambio de datos ampliamente utilizado en aplicaciones web y APIs.

Conceptos básicos

JSON es un formato de texto que resulta fácil de leer tanto para humanos como para máquinas. Su sintaxis es similar a la de los diccionarios y listas de Python, lo que facilita la conversión entre ambos formatos.

Para trabajar con JSON en Python, primero debemos importar el módulo:

import json

Serialización (Python → JSON)

La función principal para serializar datos es json.dumps() (para generar una cadena) o json.dump() (para escribir directamente a un archivo):

# Creamos un diccionario de Python
usuario = {
    "nombre": "Ana García",
    "edad": 28,
    "activo": True,
    "intereses": ["programación", "música", "senderismo"],
    "dirección": {
        "calle": "Calle Mayor 23",
        "ciudad": "Madrid",
        "código_postal": "28001"
    }
}

# Convertimos a cadena JSON
json_str = json.dumps(usuario)
print(json_str)

El resultado será una cadena JSON válida:

{"nombre": "Ana García", "edad": 28, "activo": true, "intereses": ["programación", "música", "senderismo"], "dirección": {"calle": "Calle Mayor 23", "ciudad": "Madrid", "código_postal": "28001"}}

Observa que los valores booleanos de Python (True) se han convertido a minúsculas (true) en JSON, siguiendo el estándar.

Mejorando la legibilidad

Para mejorar la legibilidad del JSON generado, podemos usar el parámetro indent:

# JSON con formato legible
json_formateado = json.dumps(usuario, indent=4)
print(json_formateado)

Esto producirá:

{
    "nombre": "Ana García",
    "edad": 28,
    "activo": true,
    "intereses": [
        "programación",
        "música",
        "senderismo"
    ],
    "dirección": {
        "calle": "Calle Mayor 23",
        "ciudad": "Madrid",
        "código_postal": "28001"
    }
}

Guardando JSON en un archivo

Para guardar directamente a un archivo, usamos json.dump():

# Guardamos el JSON en un archivo
with open("usuario.json", "w", encoding="utf-8") as archivo:
    json.dump(usuario, archivo, indent=4, ensure_ascii=False)

El parámetro ensure_ascii=False permite que los caracteres no ASCII (como acentos o ñ) se guarden directamente en lugar de convertirse a secuencias de escape Unicode, lo que mejora la legibilidad para textos en español.

Deserialización (JSON → Python)

Para convertir JSON a estructuras de datos de Python, usamos json.loads() (para cadenas) o json.load() (para archivos):

# Cadena JSON
json_data = '{"nombre": "Carlos Ruiz", "edad": 35, "habilidades": ["Python", "SQL", "JavaScript"]}'

# Convertimos a un diccionario de Python
datos_python = json.loads(json_data)

# Ahora podemos acceder como a cualquier diccionario
print(f"Nombre: {datos_python['nombre']}")
print(f"Primera habilidad: {datos_python['habilidades'][0]}")

Leyendo JSON desde un archivo

Para leer JSON desde un archivo:

# Leemos el JSON desde un archivo
with open("usuario.json", "r", encoding="utf-8") as archivo:
    datos_usuario = json.load(archivo)
    
print(f"Ciudad: {datos_usuario['dirección']['ciudad']}")

Tipos de datos compatibles

El módulo json puede serializar los siguientes tipos de datos de Python:

  • dict: Se convierte en un objeto JSON
  • list, tuple: Se convierten en arrays JSON
  • str: Se convierte en una cadena JSON
  • int, float: Se convierten en números JSON
  • True, False: Se convierten en true/false JSON
  • None: Se convierte en null JSON

Manejo de errores comunes

Es importante manejar posibles errores durante la serialización y deserialización:

try:
    with open("config.json", "r") as archivo:
        configuracion = json.load(archivo)
except FileNotFoundError:
    print("El archivo de configuración no existe. Usando valores predeterminados.")
    configuracion = {"debug": False, "timeout": 30}
except json.JSONDecodeError:
    print("El archivo de configuración contiene JSON inválido.")
    configuracion = {"debug": True, "timeout": 60}

Caso práctico: Almacenamiento de configuraciones

Un uso común de JSON es almacenar configuraciones de aplicaciones:

def guardar_configuracion(config):
    """Guarda la configuración de la aplicación en un archivo JSON."""
    try:
        with open("config.json", "w") as archivo:
            json.dump(config, archivo, indent=2)
        return True
    except Exception as e:
        print(f"Error al guardar la configuración: {e}")
        return False

def cargar_configuracion():
    """Carga la configuración desde un archivo JSON o usa valores predeterminados."""
    config_predeterminada = {
        "tema": "claro",
        "notificaciones": True,
        "idioma": "es",
        "volumen": 75
    }
    
    try:
        with open("config.json", "r") as archivo:
            return json.load(archivo)
    except (FileNotFoundError, json.JSONDecodeError):
        # Si el archivo no existe o está corrupto, guardamos la configuración predeterminada
        guardar_configuracion(config_predeterminada)
        return config_predeterminada

# Uso
config = cargar_configuracion()
print(f"Tema actual: {config['tema']}")

# Modificamos y guardamos
config["tema"] = "oscuro"
guardar_configuracion(config)

Caso práctico: Consumo de APIs

JSON es el formato estándar para comunicarse con APIs web:

import json
import urllib.request

def obtener_datos_api(url):
    """Obtiene y procesa datos JSON de una API."""
    try:
        # Realizamos la petición HTTP
        with urllib.request.urlopen(url) as respuesta:
            # Leemos y decodificamos la respuesta
            datos_json = json.loads(respuesta.read().decode())
            return datos_json
    except urllib.error.URLError as e:
        print(f"Error de conexión: {e}")
        return None
    except json.JSONDecodeError:
        print("La respuesta no contiene JSON válido")
        return None

# Ejemplo con una API pública que devuelve información sobre países
datos_pais = obtener_datos_api("https://restcountries.com/v3.1/name/spain")

if datos_pais:
    # La API devuelve una lista, tomamos el primer elemento
    pais = datos_pais[0]
    print(f"País: {pais['name']['common']}")
    print(f"Capital: {pais['capital'][0]}")
    print(f"Población: {pais['population']:,} habitantes")

La serialización y deserialización con el módulo json son operaciones fundamentales para el intercambio de datos en aplicaciones modernas. Dominar estas técnicas te permitirá trabajar eficientemente con APIs, archivos de configuración y cualquier sistema que requiera persistencia o transmisión de datos estructurados.

Trabajar con estructuras anidadas

Cuando trabajamos con JSON en Python, frecuentemente nos encontramos con estructuras anidadas que contienen múltiples niveles de objetos y arrays. Estas estructuras complejas representan relaciones jerárquicas de datos que son comunes en configuraciones de aplicaciones, respuestas de API y bases de datos documentales.

Para acceder a elementos dentro de estructuras anidadas, simplemente encadenamos los operadores de acceso (corchetes para listas y corchetes o punto para diccionarios):

import json

# Estructura JSON anidada compleja
datos_json = '''
{
    "aplicacion": "GestorTareas",
    "version": "2.5.1",
    "usuarios": [
        {
            "id": 1,
            "nombre": "Laura Martínez",
            "rol": "administrador",
            "proyectos": [
                {
                    "id": 101,
                    "nombre": "Rediseño web",
                    "tareas": [
                        {"id": 1001, "descripcion": "Wireframes", "completada": true},
                        {"id": 1002, "descripcion": "Diseño UI", "completada": false}
                    ]
                },
                {
                    "id": 102,
                    "nombre": "App móvil",
                    "tareas": [
                        {"id": 2001, "descripcion": "Prototipo", "completada": true}
                    ]
                }
            ]
        },
        {
            "id": 2,
            "nombre": "Carlos Sánchez",
            "rol": "desarrollador",
            "proyectos": []
        }
    ]
}
'''

# Convertimos la cadena JSON a un diccionario de Python
datos = json.loads(datos_json)

Ahora podemos navegar por esta estructura para extraer información específica:

# Accedemos a valores anidados
nombre_app = datos["aplicacion"]
print(f"Aplicación: {nombre_app}")

# Accedemos al primer usuario
primer_usuario = datos["usuarios"][0]
print(f"Primer usuario: {primer_usuario['nombre']}")

# Accedemos a una tarea específica del primer proyecto del primer usuario
primera_tarea = datos["usuarios"][0]["proyectos"][0]["tareas"][0]
print(f"Primera tarea: {primera_tarea['descripcion']}")

Manejo seguro de estructuras anidadas

Cuando trabajamos con estructuras anidadas, es común encontrarnos con KeyError o IndexError si intentamos acceder a claves o índices que no existen. Para evitar estos errores, podemos usar métodos más seguros:

# Método 1: Usar el método get() de los diccionarios
segundo_usuario_proyectos = datos["usuarios"][1].get("proyectos", [])
print(f"Proyectos del segundo usuario: {segundo_usuario_proyectos}")

# Método 2: Verificar la existencia antes de acceder
if "usuarios" in datos and len(datos["usuarios"]) > 2:
    tercer_usuario = datos["usuarios"][2]
    print(f"Tercer usuario: {tercer_usuario['nombre']}")
else:
    print("No hay un tercer usuario")

Recorriendo estructuras anidadas

Para procesar todos los elementos de una estructura anidada, podemos usar bucles anidados:

# Recorremos todos los usuarios y sus proyectos
for i, usuario in enumerate(datos["usuarios"]):
    print(f"\nUsuario {i+1}: {usuario['nombre']}")
    
    if not usuario["proyectos"]:
        print("  No tiene proyectos asignados")
        continue
    
    for proyecto in usuario["proyectos"]:
        print(f"  Proyecto: {proyecto['nombre']}")
        
        for tarea in proyecto["tareas"]:
            estado = "✓" if tarea["completada"] else "✗"
            print(f"    [{estado}] {tarea['descripcion']}")

Modificación de estructuras anidadas

Podemos modificar estructuras JSON anidadas como cualquier otra estructura de datos en Python:

# Añadimos un nuevo proyecto al primer usuario
nuevo_proyecto = {
    "id": 103,
    "nombre": "Análisis de datos",
    "tareas": [
        {"id": 3001, "descripcion": "Recopilación de datos", "completada": False}
    ]
}

datos["usuarios"][0]["proyectos"].append(nuevo_proyecto)

# Modificamos el estado de una tarea existente
datos["usuarios"][0]["proyectos"][0]["tareas"][1]["completada"] = True

# Añadimos un campo a todos los proyectos
for usuario in datos["usuarios"]:
    for proyecto in usuario.get("proyectos", []):
        proyecto["fecha_inicio"] = "2023-09-15"

Búsqueda en estructuras anidadas

Buscar información específica en estructuras anidadas puede ser complejo. Veamos algunas estrategias:

# Función para buscar una tarea por ID en toda la estructura
def buscar_tarea_por_id(datos, tarea_id):
    for usuario in datos["usuarios"]:
        for proyecto in usuario.get("proyectos", []):
            for tarea in proyecto.get("tareas", []):
                if tarea["id"] == tarea_id:
                    return {
                        "tarea": tarea,
                        "proyecto": proyecto["nombre"],
                        "usuario": usuario["nombre"]
                    }
    return None

# Buscamos la tarea con ID 1002
resultado = buscar_tarea_por_id(datos, 1002)
if resultado:
    print(f"\nTarea encontrada: '{resultado['tarea']['descripcion']}'")
    print(f"Asignada a: {resultado['usuario']} en el proyecto '{resultado['proyecto']}'")

Filtrado de datos anidados

Podemos usar comprensiones de listas para filtrar datos de estructuras anidadas:

# Obtenemos todas las tareas pendientes de todos los usuarios
tareas_pendientes = [
    {
        "descripcion": tarea["descripcion"],
        "proyecto": proyecto["nombre"],
        "usuario": usuario["nombre"]
    }
    for usuario in datos["usuarios"]
    for proyecto in usuario.get("proyectos", [])
    for tarea in proyecto.get("tareas", [])
    if not tarea["completada"]
]

print("\nTareas pendientes:")
for tarea in tareas_pendientes:
    print(f"- {tarea['descripcion']} ({tarea['proyecto']}, {tarea['usuario']})")

Transformación de estructuras anidadas

A veces necesitamos transformar la estructura de nuestros datos JSON:

# Transformamos la estructura para tener un formato diferente
estructura_por_proyectos = {}

for usuario in datos["usuarios"]:
    for proyecto in usuario.get("proyectos", []):
        # Si el proyecto no existe en nuestro diccionario, lo inicializamos
        if proyecto["id"] not in estructura_por_proyectos:
            estructura_por_proyectos[proyecto["id"]] = {
                "nombre": proyecto["nombre"],
                "participantes": [],
                "tareas": proyecto.get("tareas", [])
            }
        
        # Añadimos el usuario como participante
        estructura_por_proyectos[proyecto["id"]]["participantes"].append(usuario["nombre"])

# Convertimos a JSON formateado
json_transformado = json.dumps(estructura_por_proyectos, indent=2)
print(f"\nEstructura transformada:\n{json_transformado}")

Validación de estructuras anidadas

Antes de procesar datos JSON complejos, es recomendable validar su estructura:

def validar_estructura_usuario(usuario):
    """Valida que un usuario tenga la estructura esperada."""
    campos_requeridos = ["id", "nombre", "rol"]
    
    for campo in campos_requeridos:
        if campo not in usuario:
            return False
    
    # Validamos que proyectos sea una lista
    if "proyectos" not in usuario or not isinstance(usuario["proyectos"], list):
        return False
    
    return True

# Validamos todos los usuarios
usuarios_validos = all(validar_estructura_usuario(usuario) for usuario in datos["usuarios"])
print(f"\n¿Todos los usuarios tienen estructura válida? {usuarios_validos}")

Caso práctico: Configuración de aplicación multinivel

Un uso común de estructuras JSON anidadas es para configuraciones de aplicaciones con múltiples entornos:

# Configuración con valores por defecto y específicos por entorno
config_json = '''
{
    "default": {
        "database": {
            "host": "localhost",
            "port": 5432,
            "timeout": 30
        },
        "api": {
            "url": "https://api.ejemplo.com",
            "key": "default-key",
            "rate_limit": 100
        },
        "logging": {
            "level": "INFO",
            "file": "app.log"
        }
    },
    "development": {
        "database": {
            "host": "dev-db",
            "user": "dev_user"
        },
        "logging": {
            "level": "DEBUG"
        }
    },
    "production": {
        "database": {
            "host": "prod-db.ejemplo.com",
            "user": "prod_user",
            "replicas": ["replica1.ejemplo.com", "replica2.ejemplo.com"]
        },
        "api": {
            "key": "prod-secure-key"
        },
        "logging": {
            "level": "WARNING"
        }
    }
}
'''

config = json.loads(config_json)

def obtener_configuracion(entorno):
    """Obtiene la configuración combinada para un entorno específico."""
    # Comenzamos con la configuración por defecto
    config_combinada = config["default"].copy()
    
    # Si el entorno existe, actualizamos con sus valores específicos
    if entorno in config:
        # Para cada sección en el entorno específico
        for seccion, valores in config[entorno].items():
            if seccion in config_combinada:
                # Actualizamos la sección existente
                config_combinada[seccion].update(valores)
            else:
                # Añadimos la sección completa
                config_combinada[seccion] = valores
    
    return config_combinada

# Obtenemos la configuración para desarrollo
config_dev = obtener_configuracion("development")
print(f"\nConfiguración de desarrollo:")
print(f"Host BD: {config_dev['database']['host']}")
print(f"Nivel de log: {config_dev['logging']['level']}")
print(f"URL API: {config_dev['api']['url']}")

# Obtenemos la configuración para producción
config_prod = obtener_configuracion("production")
print(f"\nConfiguración de producción:")
print(f"Host BD: {config_prod['database']['host']}")
print(f"Réplicas BD: {', '.join(config_prod['database']['replicas'])}")
print(f"Clave API: {config_prod['api']['key']}")

Trabajar con estructuras JSON anidadas en Python es sencillo gracias a la similitud entre la sintaxis de JSON y las estructuras de datos nativas de Python. Con las técnicas mostradas, podrás navegar, modificar y transformar incluso las estructuras más complejas de manera eficiente.

Personalización de codificación/decodificación

El módulo json de Python ofrece mecanismos de personalización que permiten controlar con precisión cómo se serializan y deserializan los objetos. Esto resulta especialmente útil cuando trabajamos con tipos de datos que no son directamente compatibles con JSON o cuando necesitamos formatos específicos para nuestros datos.

Personalizando la serialización con parámetros

Antes de explorar soluciones avanzadas, veamos los parámetros básicos que ofrece json.dumps() para personalizar la salida:

import json

datos = {
    "nombre": "María López",
    "edad": 32,
    "coordenadas": (40.4168, -3.7038),  # Una tupla
    "idiomas": ["español", "inglés", "francés"]
}

# Personalización básica
json_personalizado = json.dumps(
    datos,
    indent=2,                # Indentación para legibilidad
    sort_keys=True,          # Ordenar claves alfabéticamente
    ensure_ascii=False,      # Permitir caracteres no ASCII directamente
    separators=(',', ': ')   # Personalizar separadores
)

print(json_personalizado)

Manejando tipos de datos no compatibles

El problema más común al serializar es encontrarse con tipos de datos que JSON no soporta nativamente, como fechas, conjuntos, tuplas o clases personalizadas. Para estos casos, podemos usar el parámetro default:

from datetime import datetime, date
import decimal

datos_complejos = {
    "fecha_actual": datetime.now(),
    "fecha_nacimiento": date(1990, 5, 15),
    "precio": decimal.Decimal("19.99"),
    "categorías": {"Python", "JSON", "Serialización"}  # Un conjunto (set)
}

# Función para convertir tipos no compatibles
def convertidor_personalizado(obj):
    if isinstance(obj, (datetime, date)):
        return obj.isoformat()
    elif isinstance(obj, decimal.Decimal):
        return float(obj)
    elif isinstance(obj, set):
        return list(obj)
    # Si no sabemos manejar el tipo, lanzamos un error
    raise TypeError(f"El objeto de tipo {type(obj)} no es serializable a JSON")

# Serialización con convertidor personalizado
try:
    json_resultado = json.dumps(
        datos_complejos, 
        default=convertidor_personalizado,
        indent=2
    )
    print(json_resultado)
except TypeError as e:
    print(f"Error de serialización: {e}")

Creando un codificador personalizado

Para casos más complejos, podemos crear una subclase de json.JSONEncoder que maneje múltiples tipos personalizados:

class EncodificadorAvanzado(json.JSONEncoder):
    def default(self, obj):
        # Manejamos fechas y horas
        if isinstance(obj, datetime):
            return {
                "__tipo__": "datetime",
                "valor": obj.isoformat()
            }
        elif isinstance(obj, date):
            return {
                "__tipo__": "date",
                "valor": obj.isoformat()
            }
        # Manejamos números decimales
        elif isinstance(obj, decimal.Decimal):
            return {
                "__tipo__": "decimal",
                "valor": str(obj)
            }
        # Manejamos conjuntos
        elif isinstance(obj, set):
            return {
                "__tipo__": "set",
                "valor": list(obj)
            }
        # Manejamos tuplas (preservando que es una tupla)
        elif isinstance(obj, tuple):
            return {
                "__tipo__": "tuple",
                "valor": list(obj)
            }
        # Delegamos a la implementación padre para tipos estándar
        return super().default(obj)

# Usamos nuestro codificador personalizado
json_avanzado = json.dumps(
    datos_complejos,
    cls=EncodificadorAvanzado,
    indent=2
)

print(json_avanzado)

Este enfoque no solo convierte los tipos no compatibles, sino que también preserva información sobre el tipo original mediante metadatos, lo que facilita la deserialización posterior.

Personalizando la deserialización

Para deserializar los objetos personalizados, necesitamos un decodificador personalizado que interprete los metadatos que añadimos durante la serialización:

class DecodificadorAvanzado(json.JSONDecoder):
    def __init__(self, *args, **kwargs):
        # Configuramos el decodificador con un hook para objetos
        json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
    
    def object_hook(self, obj):
        # Verificamos si el objeto tiene nuestro marcador de tipo
        if "__tipo__" not in obj:
            return obj
        
        tipo = obj["__tipo__"]
        valor = obj["valor"]
        
        # Reconstruimos el objeto según su tipo
        if tipo == "datetime":
            return datetime.fromisoformat(valor)
        elif tipo == "date":
            return date.fromisoformat(valor)
        elif tipo == "decimal":
            return decimal.Decimal(valor)
        elif tipo == "set":
            return set(valor)
        elif tipo == "tuple":
            return tuple(valor)
        
        # Si no reconocemos el tipo, devolvemos el objeto sin modificar
        return obj

# Deserializamos usando nuestro decodificador personalizado
datos_reconstruidos = json.loads(json_avanzado, cls=DecodificadorAvanzado)

# Verificamos que los tipos se hayan reconstruido correctamente
print(f"Fecha actual: {datos_reconstruidos['fecha_actual']} (tipo: {type(datos_reconstruidos['fecha_actual']).__name__})")
print(f"Categorías: {datos_reconstruidos['categorías']} (tipo: {type(datos_reconstruidos['categorías']).__name__})")

Usando el parámetro object_hook

Una alternativa más sencilla para personalizar la deserialización es usar directamente el parámetro object_hook:

def reconstruir_objetos(obj):
    # Verificamos si hay marcadores de tipo en el objeto
    if "__tipo__" in obj:
        if obj["__tipo__"] == "datetime":
            return datetime.fromisoformat(obj["valor"])
        elif obj["__tipo__"] == "set":
            return set(obj["valor"])
        # ... otros tipos
    return obj

# Deserializamos con el hook
datos_simples = json.loads(json_avanzado, object_hook=reconstruir_objetos)

Serializando objetos personalizados

Uno de los casos más comunes es necesitar serializar instancias de clases personalizadas. Veamos cómo hacerlo:

class Producto:
    def __init__(self, id, nombre, precio, stock=0):
        self.id = id
        self.nombre = nombre
        self.precio = precio
        self.stock = stock
    
    def __repr__(self):
        return f"Producto({self.id}, {self.nombre}, {self.precio}, {self.stock})"

# Método 1: Añadir método to_json a la clase
class ProductoSerializable(Producto):
    def to_json(self):
        return {
            "id": self.id,
            "nombre": self.nombre,
            "precio": float(self.precio),
            "stock": self.stock
        }

# Método 2: Usar un codificador personalizado
class CodificadorProducto(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Producto):
            return {
                "__tipo__": "Producto",
                "id": obj.id,
                "nombre": obj.nombre,
                "precio": float(obj.precio) if isinstance(obj.precio, decimal.Decimal) else obj.precio,
                "stock": obj.stock
            }
        return super().default(obj)

# Creamos algunos productos
productos = [
    ProductoSerializable(1, "Teclado mecánico", decimal.Decimal("89.99"), 15),
    ProductoSerializable(2, "Ratón inalámbrico", decimal.Decimal("45.50"), 8)
]

# Método 1: Usando el método to_json
json_productos1 = json.dumps([p.to_json() for p in productos], indent=2)

# Método 2: Usando el codificador personalizado
json_productos2 = json.dumps(productos, cls=CodificadorProducto, indent=2)

print("Método 1 (to_json):")
print(json_productos1)
print("\nMétodo 2 (codificador personalizado):")
print(json_productos2)

Deserializando a objetos personalizados

Para reconstruir objetos personalizados durante la deserialización:

def decodificador_producto(obj):
    if "__tipo__" in obj and obj["__tipo__"] == "Producto":
        return Producto(
            id=obj["id"],
            nombre=obj["nombre"],
            precio=decimal.Decimal(str(obj["precio"])),
            stock=obj["stock"]
        )
    return obj

# Deserializamos a objetos Producto
productos_reconstruidos = json.loads(json_productos2, object_hook=decodificador_producto)

# Verificamos que son instancias de Producto
for p in productos_reconstruidos:
    print(f"{p} (tipo: {type(p).__name__})")

Caso práctico: Configuración con tipos personalizados

Un caso de uso común es almacenar configuraciones con tipos de datos especiales:

from pathlib import Path
import re

class ConfiguracionApp:
    def __init__(self):
        self.ruta_datos = Path("/var/data")
        self.patrones_busqueda = [
            re.compile(r"^\d{4}-\d{2}-\d{2}$"),  # Formato fecha
            re.compile(r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$")  # Email
        ]
        self.fecha_actualizacion = datetime.now()
        self.limites_api = {
            "basico": 1000,
            "premium": 10000
        }

class CodificadorConfig(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, ConfiguracionApp):
            # Convertimos el objeto completo a un diccionario
            return {
                "__tipo__": "ConfiguracionApp",
                "ruta_datos": str(obj.ruta_datos),
                "patrones_busqueda": [
                    {"patron": p.pattern} for p in obj.patrones_busqueda
                ],
                "fecha_actualizacion": obj.fecha_actualizacion.isoformat(),
                "limites_api": obj.limites_api
            }
        elif isinstance(obj, Path):
            return {"__tipo__": "Path", "ruta": str(obj)}
        elif isinstance(obj, re.Pattern):
            return {"__tipo__": "Regex", "patron": obj.pattern}
        elif isinstance(obj, datetime):
            return {"__tipo__": "datetime", "valor": obj.isoformat()}
        return super().default(obj)

def decodificador_config(obj):
    if "__tipo__" not in obj:
        return obj
    
    if obj["__tipo__"] == "ConfiguracionApp":
        config = ConfiguracionApp()
        config.ruta_datos = Path(obj["ruta_datos"])
        config.patrones_busqueda = [
            re.compile(item["patron"]) for item in obj["patrones_busqueda"]
        ]
        config.fecha_actualizacion = datetime.fromisoformat(obj["fecha_actualizacion"])
        config.limites_api = obj["limites_api"]
        return config
    elif obj["__tipo__"] == "Path":
        return Path(obj["ruta"])
    elif obj["__tipo__"] == "Regex":
        return re.compile(obj["patron"])
    elif obj["__tipo__"] == "datetime":
        return datetime.fromisoformat(obj["valor"])
    
    return obj

# Creamos y guardamos la configuración
config = ConfiguracionApp()
json_config = json.dumps(config, cls=CodificadorConfig, indent=2)

with open("config_app.json", "w") as f:
    f.write(json_config)

# Cargamos la configuración
with open("config_app.json", "r") as f:
    config_cargada = json.loads(f.read(), object_hook=decodificador_config)

# Verificamos que se ha reconstruido correctamente
print(f"Ruta de datos: {config_cargada.ruta_datos} (tipo: {type(config_cargada.ruta_datos).__name__})")
print(f"Primer patrón: {config_cargada.patrones_busqueda[0].pattern}")
print(f"Fecha actualización: {config_cargada.fecha_actualizacion}")

Personalizando la serialización con dataclasses

Las dataclasses de Python (disponibles desde Python 3.7) facilitan la serialización:

from dataclasses import dataclass, asdict, field
from typing import List, Dict, Optional

@dataclass
class Usuario:
    id: int
    nombre: str
    email: str
    activo: bool = True
    roles: List[str] = field(default_factory=list)
    preferencias: Dict[str, str] = field(default_factory=dict)
    ultima_conexion: Optional[datetime] = None

# Creamos un usuario
usuario = Usuario(
    id=1,
    nombre="Elena García",
    email="elena@ejemplo.com",
    roles=["editor", "revisor"],
    preferencias={"tema": "oscuro", "notificaciones": "diarias"},
    ultima_conexion=datetime.now()
)

# Convertimos a diccionario y luego a JSON
# (necesitamos un encoder personalizado para el campo datetime)
usuario_dict = asdict(usuario)
json_usuario = json.dumps(
    usuario_dict,
    default=lambda o: o.isoformat() if isinstance(o, datetime) else o,
    indent=2
)

print(json_usuario)

La personalización de la codificación y decodificación JSON en Python te permite trabajar con cualquier tipo de dato, manteniendo la integridad de la información y facilitando la interoperabilidad entre sistemas. Estas técnicas son fundamentales para desarrollar aplicaciones robustas que necesitan persistir o transmitir datos complejos.

JSON y diccionarios Python

Los diccionarios en Python y los objetos JSON comparten una estructura similar basada en pares clave-valor, lo que crea una relación natural entre ambos. Esta similitud hace que la conversión entre diccionarios Python y JSON sea particularmente fluida, convirtiéndose en uno de los casos de uso más comunes del módulo json.

Similitudes estructurales

Un diccionario Python se parece mucho a un objeto JSON:

# Diccionario Python
usuario_dict = {
    "nombre": "Javier Pérez",
    "edad": 34,
    "activo": True,
    "habilidades": ["Python", "SQL", "Docker"]
}

# Equivalente en JSON
"""
{
    "nombre": "Javier Pérez",
    "edad": 34,
    "activo": true,
    "habilidades": ["Python", "SQL", "Docker"]
}
"""

Esta similitud estructural facilita el trabajo con APIs, configuraciones y almacenamiento de datos estructurados.

Diferencias clave entre diccionarios y JSON

A pesar de sus similitudes, existen algunas diferencias importantes que debemos tener en cuenta:

  • Tipos de claves: Los diccionarios Python pueden usar cualquier objeto inmutable como clave (strings, números, tuplas), mientras que JSON solo permite strings.
  • Tipos de datos: JSON tiene un conjunto más limitado de tipos de datos que Python.
  • Sintaxis: Los diccionarios Python usan True/False/None, mientras que JSON usa true/false/null.
import json

# Diccionario con claves no string
dict_python = {
    "texto": "valor",
    42: "número como clave",
    (1, 2): "tupla como clave",
    True: "booleano como clave"
}

# Esto generará un error
try:
    json_str = json.dumps(dict_python)
except TypeError as e:
    print(f"Error: {e}")
    # Salida: Error: keys must be str, int, float, bool or None, not tuple

Conversión automática de tipos

El módulo json realiza conversiones automáticas entre tipos de Python y JSON:

import json

# Mapeo de tipos Python → JSON
tipos_python = {
    "string": "texto",
    "int": 42,
    "float": 3.14,
    "bool_true": True,
    "bool_false": False,
    "none": None,
    "lista": [1, 2, 3],
    "dict": {"clave": "valor"}
}

json_str = json.dumps(tipos_python, indent=2)
print(f"Python → JSON:\n{json_str}")

# Mapeo de tipos JSON → Python
python_obj = json.loads(json_str)
print("\nJSON → Python:")
for clave, valor in python_obj.items():
    print(f"{clave}: {valor} (tipo: {type(valor).__name__})")

Normalización de diccionarios para JSON

Para asegurar que un diccionario sea serializable a JSON, podemos crear una función de normalización:

def normalizar_para_json(diccionario):
    """Convierte un diccionario con claves no string a uno compatible con JSON."""
    resultado = {}
    
    for clave, valor in diccionario.items():
        # Convertimos la clave a string
        clave_str = str(clave)
        
        # Procesamos el valor recursivamente si es necesario
        if isinstance(valor, dict):
            valor = normalizar_para_json(valor)
        elif isinstance(valor, (list, tuple)):
            valor = [
                normalizar_para_json(item) if isinstance(item, dict) else item
                for item in valor
            ]
        
        resultado[clave_str] = valor
    
    return resultado

# Diccionario con claves problemáticas
dict_complejo = {
    "normal": "valor",
    42: "número",
    (1, 2): "tupla",
    True: "booleano",
    "anidado": {
        5: "cinco",
        "lista": [{"x": 1}, {(1, 2): "tupla en lista"}]
    }
}

# Normalizamos y convertimos a JSON
dict_normalizado = normalizar_para_json(dict_complejo)
json_normalizado = json.dumps(dict_normalizado, indent=2)
print(json_normalizado)

Preservando tipos de claves

Si necesitamos preservar información sobre los tipos originales de las claves para una posterior reconstrucción:

def codificar_con_tipos(diccionario):
    """Codifica un diccionario preservando información de tipos de claves."""
    resultado = {"__tipo__": "dict_tipado", "items": []}
    
    for clave, valor in diccionario.items():
        tipo_clave = type(clave).__name__
        clave_str = str(clave)
        
        # Procesamos el valor recursivamente si es necesario
        if isinstance(valor, dict):
            valor_procesado = codificar_con_tipos(valor)
        else:
            valor_procesado = valor
        
        resultado["items"].append({
            "key_type": tipo_clave,
            "key": clave_str,
            "value": valor_procesado
        })
    
    return resultado

# Decodificamos recuperando los tipos originales
def decodificar_con_tipos(obj):
    if isinstance(obj, dict) and obj.get("__tipo__") == "dict_tipado":
        resultado = {}
        
        for item in obj["items"]:
            tipo_clave = item["key_type"]
            clave_str = item["key"]
            valor = item["value"]
            
            # Convertimos la clave al tipo original
            if tipo_clave == "int":
                clave = int(clave_str)
            elif tipo_clave == "float":
                clave = float(clave_str)
            elif tipo_clave == "bool":
                clave = clave_str.lower() == "true"
            elif tipo_clave == "tuple":
                # Asumimos que la tupla se guardó como string de su representación
                clave = eval(clave_str)
            else:
                clave = clave_str
            
            # Procesamos el valor recursivamente si es necesario
            if isinstance(valor, dict) and valor.get("__tipo__") == "dict_tipado":
                valor = decodificar_con_tipos(valor)
            
            resultado[clave] = valor
        
        return resultado
    
    return obj

# Ejemplo de uso
dict_original = {
    "texto": "valor",
    42: "número",
    (1, 2): "tupla",
    True: "booleano"
}

# Codificamos preservando tipos
dict_codificado = codificar_con_tipos(dict_original)
json_tipado = json.dumps(dict_codificado, indent=2)

# Decodificamos recuperando tipos
dict_recuperado = decodificar_con_tipos(json.loads(json_tipado))

print("Diccionario recuperado:")
for k, v in dict_recuperado.items():
    print(f"{k} ({type(k).__name__}): {v}")

Uso de diccionarios como configuración

Un caso de uso común es utilizar diccionarios para gestionar configuraciones de aplicaciones:

import json
import os

class ConfigManager:
    def __init__(self, config_file="config.json"):
        self.config_file = config_file
        self.config = self._load_config()
    
    def _load_config(self):
        """Carga la configuración desde un archivo JSON o usa valores predeterminados."""
        default_config = {
            "app": {
                "name": "MiAplicación",
                "version": "1.0.0",
                "debug": False
            },
            "database": {
                "host": "localhost",
                "port": 5432,
                "user": "usuario",
                "password": "contraseña"
            },
            "features": {
                "dark_mode": True,
                "auto_save": True,
                "save_interval": 300  # segundos
            }
        }
        
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, "r", encoding="utf-8") as f:
                    user_config = json.load(f)
                
                # Actualizamos recursivamente la configuración predeterminada
                return self._deep_update(default_config, user_config)
            else:
                # Si no existe el archivo, lo creamos con la configuración predeterminada
                self.save_config(default_config)
                return default_config
        except Exception as e:
            print(f"Error al cargar la configuración: {e}")
            return default_config
    
    def _deep_update(self, original, update):
        """Actualiza recursivamente un diccionario con otro."""
        for key, value in update.items():
            if key in original and isinstance(original[key], dict) and isinstance(value, dict):
                self._deep_update(original[key], value)
            else:
                original[key] = value
        return original
    
    def save_config(self, config=None):
        """Guarda la configuración en un archivo JSON."""
        if config is None:
            config = self.config
        
        try:
            with open(self.config_file, "w", encoding="utf-8") as f:
                json.dump(config, f, indent=2, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"Error al guardar la configuración: {e}")
            return False
    
    def get(self, section, key, default=None):
        """Obtiene un valor de configuración específico."""
        try:
            return self.config[section][key]
        except KeyError:
            return default
    
    def set(self, section, key, value):
        """Establece un valor de configuración específico."""
        if section not in self.config:
            self.config[section] = {}
        
        self.config[section][key] = value
        return self.save_config()

# Ejemplo de uso
config = ConfigManager()

# Obtenemos valores
app_name = config.get("app", "name")
debug_mode = config.get("app", "debug")
print(f"Aplicación: {app_name}, Modo debug: {debug_mode}")

# Modificamos valores
config.set("app", "debug", True)
config.set("features", "auto_save", False)

# Añadimos una nueva sección
config.set("ui", "theme", "dark")
config.set("ui", "font_size", 14)

Diccionarios anidados y acceso por puntos

A veces es más conveniente acceder a diccionarios anidados usando notación de puntos en lugar de corchetes:

class DotDict:
    """Clase que permite acceder a diccionarios anidados usando notación de puntos."""
    def __init__(self, data):
        for key, value in data.items():
            if isinstance(value, dict):
                setattr(self, key, DotDict(value))
            else:
                setattr(self, key, value)
    
    def to_dict(self):
        """Convierte de nuevo a un diccionario estándar."""
        result = {}
        for key, value in self.__dict__.items():
            if isinstance(value, DotDict):
                result[key] = value.to_dict()
            else:
                result[key] = value
        return result

# Cargamos un JSON y lo convertimos a un objeto con acceso por puntos
with open("config.json", "r") as f:
    config_data = json.load(f)

config = DotDict(config_data)

# Ahora podemos acceder así:
print(f"Nombre: {config.app.name}")
print(f"Host DB: {config.database.host}")
print(f"Modo oscuro: {config.features.dark_mode}")

# Y convertir de nuevo a diccionario para guardar
updated_dict = config.to_dict()
with open("config_updated.json", "w") as f:
    json.dump(updated_dict, f, indent=2)

Caso práctico: Almacenamiento de preferencias de usuario

Un ejemplo práctico de uso de JSON y diccionarios es almacenar preferencias de usuario:

import json
import os
from datetime import datetime

class UserPreferences:
    def __init__(self, user_id):
        self.user_id = user_id
        self.prefs_file = f"user_{user_id}_prefs.json"
        self.preferences = self._load_preferences()
    
    def _load_preferences(self):
        """Carga las preferencias del usuario desde un archivo JSON."""
        default_prefs = {
            "user_id": self.user_id,
            "last_updated": datetime.now().isoformat(),
            "ui": {
                "theme": "light",
                "font_size": 12,
                "notifications": True
            },
            "content": {
                "language": "es",
                "items_per_page": 20,
                "show_thumbnails": True
            },
            "privacy": {
                "share_data": False,
                "allow_cookies": True
            },
            "recent_searches": [],
            "favorite_items": []
        }
        
        if not os.path.exists(self.prefs_file):
            return default_prefs
        
        try:
            with open(self.prefs_file, "r", encoding="utf-8") as f:
                user_prefs = json.load(f)
            return user_prefs
        except Exception as e:
            print(f"Error al cargar preferencias: {e}")
            return default_prefs
    
    def save(self):
        """Guarda las preferencias actuales en el archivo JSON."""
        self.preferences["last_updated"] = datetime.now().isoformat()
        
        try:
            with open(self.prefs_file, "w", encoding="utf-8") as f:
                json.dump(self.preferences, f, indent=2, ensure_ascii=False)
            return True
        except Exception as e:
            print(f"Error al guardar preferencias: {e}")
            return False
    
    def update_ui(self, **kwargs):
        """Actualiza preferencias de interfaz de usuario."""
        for key, value in kwargs.items():
            if key in self.preferences["ui"]:
                self.preferences["ui"][key] = value
        return self.save()
    
    def add_recent_search(self, search_term):
        """Añade un término de búsqueda a la lista de búsquedas recientes."""
        # Evitamos duplicados y mantenemos solo las 10 búsquedas más recientes
        if search_term in self.preferences["recent_searches"]:
            self.preferences["recent_searches"].remove(search_term)
        
        self.preferences["recent_searches"].insert(0, search_term)
        self.preferences["recent_searches"] = self.preferences["recent_searches"][:10]
        return self.save()
    
    def add_favorite(self, item_id):
        """Añade un elemento a favoritos."""
        if item_id not in self.preferences["favorite_items"]:
            self.preferences["favorite_items"].append(item_id)
            return self.save()
        return True
    
    def remove_favorite(self, item_id):
        """Elimina un elemento de favoritos."""
        if item_id in self.preferences["favorite_items"]:
            self.preferences["favorite_items"].remove(item_id)
            return self.save()
        return True

# Ejemplo de uso
user_prefs = UserPreferences(user_id=12345)

# Actualizamos preferencias de UI
user_prefs.update_ui(theme="dark", font_size=14)

# Añadimos búsquedas recientes
user_prefs.add_recent_search("python json tutorial")
user_prefs.add_recent_search("mejores prácticas python")

# Añadimos favoritos
user_prefs.add_favorite("article_789")
user_prefs.add_favorite("video_456")

print(f"Preferencias actualizadas: {user_prefs.preferences}")

Validación de esquemas de diccionarios

Cuando trabajamos con datos JSON que deben seguir una estructura específica, es útil validar que los diccionarios cumplan con el esquema esperado:

def validar_esquema(diccionario, esquema, ruta=""):
    """Valida que un diccionario cumpla con un esquema definido."""
    errores = []
    
    # Verificamos campos requeridos
    for campo, config in esquema.items():
        campo_ruta = f"{ruta}.{campo}" if ruta else campo
        
        # Verificamos si el campo existe
        if campo not in diccionario:
            if config.get("requerido", False):
                errores.append(f"Campo requerido '{campo_ruta}' no encontrado")
            continue
        
        valor = diccionario[campo]
        tipo_esperado = config.get("tipo")
        
        # Verificamos el tipo
        if tipo_esperado and not isinstance(valor, tipo_esperado):
            errores.append(
                f"Campo '{campo_ruta}' debe ser de tipo {tipo_esperado.__name__}, "
                f"pero es {type(valor).__name__}"
            )
        
        # Verificamos esquema anidado
        if "esquema" in config and isinstance(valor, dict):
            errores.extend(validar_esquema(valor, config["esquema"], campo_ruta))
        
        # Verificamos lista de objetos
        if "items_esquema" in config and isinstance(valor, list):
            for i, item in enumerate(valor):
                if isinstance(item, dict):
                    errores.extend(validar_esquema(
                        item, config["items_esquema"], f"{campo_ruta}[{i}]"
                    ))
    
    return errores

# Definimos un esquema para validar
esquema_usuario = {
    "id": {"tipo": int, "requerido": True},
    "nombre": {"tipo": str, "requerido": True},
    "email": {"tipo": str, "requerido": True},
    "edad": {"tipo": int},
    "direccion": {
        "tipo": dict,
        "esquema": {
            "calle": {"tipo": str, "requerido": True},
            "ciudad": {"tipo": str, "requerido": True},
            "codigo_postal": {"tipo": str}
        }
    },
    "telefonos": {
        "tipo": list,
        "items_esquema": {
            "tipo": {"tipo": str, "requerido": True},
            "numero": {"tipo": str, "requerido": True}
        }
    }
}

# Datos a validar
datos_usuario = {
    "id": 1001,
    "nombre": "Roberto Gómez",
    "email": "roberto@ejemplo.com",
    "direccion": {
        "calle": "Av. Principal 123",
        "ciudad": "Barcelona"
    },
    "telefonos": [
        {"tipo": "casa", "numero": "555-1234"},
        {"tipo": "movil", "numero": "666-5678"}
    ]
}

# Validamos
errores = validar_esquema(datos_usuario, esquema_usuario)
if errores:
    print("Errores de validación:")
    for error in errores:
        print(f"- {error}")
else:
    print("Los datos son válidos según el esquema")

La estrecha relación entre diccionarios Python y JSON facilita enormemente el trabajo con datos estructurados, configuraciones y APIs. Aprovechando esta sinergia natural, podemos crear aplicaciones robustas que manejen datos complejos de manera eficiente y mantenible.

Aprende Python online

Otras lecciones de Python

Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Introducción A Python

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

Ejercicios de programación de Python

Evalúa tus conocimientos de esta lección Módulo json con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Módulo math

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la serialización y deserialización de datos con el módulo json.
  • Navegar, modificar y transformar estructuras JSON anidadas en Python.
  • Personalizar la codificación y decodificación de tipos de datos no compatibles con JSON.
  • Entender la relación entre diccionarios Python y objetos JSON, incluyendo diferencias y conversiones.
  • Aplicar técnicas para validar, almacenar y gestionar configuraciones y preferencias usando JSON y diccionarios.