Ir al contenido principal

Trabajando con JSON

Introducción

JSON (JavaScript Object Notation) es un formato de intercambio de datos ligero, fácil de leer y escribir para los humanos, y sencillo de analizar y generar para las máquinas. En Python, el módulo json proporciona funciones para trabajar con este formato, permitiendo convertir estructuras de datos de Python a cadenas JSON y viceversa. Esta capacidad es fundamental para interactuar con APIs web, almacenar configuraciones o intercambiar información entre sistemas.

En este artículo aprenderemos cómo utilizar el módulo json para serializar y deserializar datos, manipular estructuras JSON complejas y aplicar buenas prácticas en el manejo de este formato tan utilizado en el desarrollo moderno.

El formato JSON

JSON utiliza una sintaxis similar a los diccionarios y listas de Python, lo que hace que sea muy intuitivo para los programadores de este lenguaje:

  • Los objetos JSON se representan entre llaves {} con pares clave-valor (similar a los diccionarios)
  • Los arrays JSON se representan entre corchetes [] (similar a las listas)
  • Las cadenas de texto deben estar entre comillas dobles ""
  • Los números pueden ser enteros o decimales
  • Los valores booleanos son true o false (en minúsculas)
  • El valor nulo se representa como null

El módulo json en Python

Para trabajar con JSON en Python, simplemente importamos el módulo json:

import json

Serialización: De Python a JSON

La serialización es el proceso de convertir estructuras de datos de Python a una cadena JSON:

# Diccionario de Python
datos_python = {
    "nombre": "María",
    "edad": 28,
    "ciudad": "Madrid",
    "hobbies": ["lectura", "senderismo", "fotografía"],
    "activo": True,
    "altura": 1.65
}

# Convertir a JSON
json_string = json.dumps(datos_python)
print(json_string)
# {"nombre": "María", "edad": 28, "ciudad": "Madrid", "hobbies": ["lectura", "senderismo", "fotografía"], "activo": true, "altura": 1.65}

La función dumps() (dump string) convierte un objeto Python a una cadena JSON.

Deserialización: De JSON a Python

La deserialización es el proceso inverso, convertir una cadena JSON a estructuras de datos de Python:

# Cadena JSON
json_data = '{"nombre": "Carlos", "edad": 32, "ciudad": "Barcelona", "hobbies": ["ajedrez", "cocina"], "activo": false}'

# Convertir a Python
datos_python = json.loads(json_data)
print(datos_python)
# {'nombre': 'Carlos', 'edad': 32, 'ciudad': 'Barcelona', 'hobbies': ['ajedrez', 'cocina'], 'activo': False}

# Ahora podemos acceder a los datos como un diccionario normal
print(f"Nombre: {datos_python['nombre']}")
print(f"Primer hobby: {datos_python['hobbies'][0]}")

La función loads() (load string) convierte una cadena JSON a un objeto Python.

Mapeo entre tipos de datos

Al convertir entre Python y JSON, los tipos de datos se mapean de la siguiente manera:

Python JSON
dict object
list, tuple array
str string
int, float number
True true
False false
None null

Trabajando con archivos JSON

Una operación común es leer y escribir datos JSON en archivos:

Guardar datos en un archivo JSON

datos = {
    "empleados": [
        {"id": 1, "nombre": "Ana López", "departamento": "Desarrollo"},
        {"id": 2, "nombre": "Pedro Gómez", "departamento": "Marketing"},
        {"id": 3, "nombre": "Laura Martínez", "departamento": "Finanzas"}
    ],
    "ubicacion": "Madrid",
    "activo": True
}

# Guardar en un archivo
with open("empresa.json", "w", encoding="utf-8") as archivo:
    json.dump(datos, archivo, ensure_ascii=False, indent=4)
    
print("Datos guardados en empresa.json")

Observe que usamos dump() (sin 's') para escribir directamente en un archivo. El parámetro ensure_ascii=False permite guardar caracteres no ASCII (como tildes) correctamente, y indent=4 formatea el JSON para que sea más legible.

Leer datos desde un archivo JSON

# Leer desde un archivo
try:
    with open("empresa.json", "r", encoding="utf-8") as archivo:
        datos_leidos = json.load(archivo)
    
    print("Datos leídos del archivo:")
    for empleado in datos_leidos["empleados"]:
        print(f"ID: {empleado['id']}, Nombre: {empleado['nombre']}")
except FileNotFoundError:
    print("El archivo no existe")

Aquí usamos load() (sin 's') para leer directamente desde un archivo.

Personalización de la serialización

Dar formato a la salida JSON

Para mejorar la legibilidad, podemos personalizar el formato de la salida JSON:

datos = {"nombre": "Juan", "cursos": ["Python", "JavaScript", "SQL"]}

# JSON en una línea
print(json.dumps(datos))

# JSON con formato legible
print(json.dumps(datos, indent=2))

# JSON ordenado por claves
print(json.dumps(datos, sort_keys=True))

# JSON con formato y ordenado
print(json.dumps(datos, indent=2, sort_keys=True))

Manejo de tipos de datos personalizados

JSON solo soporta un conjunto limitado de tipos de datos. Para serializar objetos personalizados, necesitamos convertirlos primero:

from datetime import datetime

class Persona:
    def __init__(self, nombre, fecha_nacimiento):
        self.nombre = nombre
        self.fecha_nacimiento = fecha_nacimiento

# Función para convertir objetos personalizados a diccionarios
def convertir_a_serializable(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    elif isinstance(obj, Persona):
        return {"nombre": obj.nombre, "fecha_nacimiento": obj.fecha_nacimiento}
    else:
        return str(obj)

# Crear objetos
persona = Persona("Elena", datetime(1990, 5, 15))

# Serializar usando el convertidor personalizado
json_string = json.dumps(persona, default=convertir_a_serializable)
print(json_string)
# {"nombre": "Elena", "fecha_nacimiento": "1990-05-15T00:00:00"}

Manejo de errores comunes

Al trabajar con JSON pueden surgir varios errores:

Errores de sintaxis

# JSON con error de sintaxis (comilla simple)
json_erroneo = "{'nombre': 'Pablo'}"

try:
    datos = json.loads(json_erroneo)
except json.JSONDecodeError as e:
    print(f"Error de sintaxis: {e}")
    # Corregimos el error
    json_correcto = '{"nombre": "Pablo"}'
    datos = json.loads(json_correcto)
    print("Datos corregidos:", datos)

Codificación de caracteres

texto_con_tildes = '{"descripción": "Análisis económico"}'

# Para evitar problemas con caracteres especiales
datos = json.loads(texto_con_tildes)
print(datos)

# Al guardar, asegurarse de mantener caracteres especiales
with open("datos_utf8.json", "w", encoding="utf-8") as f:
    json.dump(datos, f, ensure_ascii=False)

JSON y APIs web

Una de las aplicaciones más comunes de JSON es en la comunicación con APIs web:

import json
import urllib.request

try:
    # Obtener datos de una API pública
    url = "https://jsonplaceholder.typicode.com/users/1"
    with urllib.request.urlopen(url) as respuesta:
        datos_json = json.loads(respuesta.read().decode())
    
    # Procesar los datos
    print(f"Usuario: {datos_json['name']}")
    print(f"Email: {datos_json['email']}")
    print(f"Ciudad: {datos_json['address']['city']}")
except Exception as e:
    print(f"Error al obtener datos: {e}")

Validación de JSON

Para asegurarnos de que un JSON contiene la estructura esperada:

def validar_json_usuario(datos):
    campos_requeridos = ["nombre", "edad", "email"]
    
    # Verificar campos requeridos
    for campo in campos_requeridos:
        if campo not in datos:
            return False, f"Falta el campo requerido: {campo}"
    
    # Verificar tipos de datos
    if not isinstance(datos["nombre"], str):
        return False, "El nombre debe ser una cadena de texto"
    
    if not isinstance(datos["edad"], int):
        return False, "La edad debe ser un número entero"
    
    if not isinstance(datos["email"], str) or "@" not in datos["email"]:
        return False, "Email inválido"
    
    return True, "JSON válido"

# Ejemplo de uso
json_string = '{"nombre": "Roberto", "edad": 25, "email": "roberto@ejemplo.com"}'
datos = json.loads(json_string)
valido, mensaje = validar_json_usuario(datos)
print(mensaje)  # JSON válido

json_string = '{"nombre": "Roberto", "edad": "veinticinco", "email": "roberto@ejemplo.com"}'
datos = json.loads(json_string)
valido, mensaje = validar_json_usuario(datos)
print(mensaje)  # La edad debe ser un número entero

JSON vs diccionarios de Python

Aunque parecidos, hay diferencias importantes entre JSON y los diccionarios de Python:

  1. Sintaxis: En JSON las claves deben ser cadenas entre comillas dobles
  2. Tipos de datos: JSON no tiene tuplas, conjuntos o bytes
  3. Booleanos y nulos: En JSON son true, false y null (en minúsculas)
  4. Comentarios: JSON no admite comentarios

Ejemplos prácticos

Configuración de aplicación

# Configuración por defecto
config_default = {
    "idioma": "es",
    "tema": "claro",
    "notificaciones": True,
    "volumen": 80
}

# Guardar configuración
def guardar_config(config):
    with open("config.json", "w") as archivo:
        json.dump(config, archivo, indent=2)

# Cargar configuración
def cargar_config():
    try:
        with open("config.json", "r") as archivo:
            return json.load(archivo)
    except (FileNotFoundError, json.JSONDecodeError):
        # Si el archivo no existe o está corrupto, usar configuración por defecto
        return config_default

# Ejemplo de uso
configuracion = cargar_config()
print(f"Configuración cargada: {configuracion}")

# Modificar configuración
configuracion["tema"] = "oscuro"
guardar_config(configuracion)

Análisis de datos sencillo

# Datos de ventas mensuales
ventas_json = '''
{
    "ventas": [
        {"mes": "enero", "cantidad": 1200},
        {"mes": "febrero", "cantidad": 1500},
        {"mes": "marzo", "cantidad": 1100},
        {"mes": "abril", "cantidad": 1800}
    ]
}
'''

# Cargar datos
datos = json.loads(ventas_json)

# Calcular total y promedio
total_ventas = sum(item["cantidad"] for item in datos["ventas"])
promedio_ventas = total_ventas / len(datos["ventas"])

print(f"Total de ventas: {total_ventas}")
print(f"Promedio mensual: {promedio_ventas:.2f}")

# Encontrar el mes con mayores ventas
mes_max_ventas = max(datos["ventas"], key=lambda x: x["cantidad"])
print(f"Mes con más ventas: {mes_max_ventas['mes']} ({mes_max_ventas['cantidad']})")

Resumen

JSON es un formato de intercambio de datos esencial en el desarrollo moderno, especialmente en aplicaciones web y APIs. El módulo json de Python proporciona herramientas potentes y sencillas para trabajar con este formato, permitiendo convertir datos entre Python y JSON, manipular archivos JSON y personalizar el proceso de serialización y deserialización.

Dominar el trabajo con JSON te permitirá integrar tus aplicaciones Python con servicios web, almacenar datos de forma estructurada y compartir información entre diferentes sistemas. En los próximos temas exploraremos otras bibliotecas como NumPy y Pandas que complementan estas capacidades para análisis de datos más avanzados.