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
ofalse
(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:
- Sintaxis: En JSON las claves deben ser cadenas entre comillas dobles
- Tipos de datos: JSON no tiene tuplas, conjuntos o bytes
- Booleanos y nulos: En JSON son
true
,false
ynull
(en minúsculas) - 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.