JSON (JavaScript Object Notation) es un formato ligero de intercambio de datos que es fácil de leer y escribir para los humanos, y fácil de parsear y generar para las máquinas. Es un formato de texto que utiliza una sintaxis basada en objetos y arrays de JavaScript.
1Estructura de JSON¶
Un archivo JSON puede contener un único valor, de los tipos de datos existentes en JavaScript. Es decir, números, strings, booleanos, null, objetos y arrays.
Si bien la sintaxis y los tipos soportados son los de JavaScript, existen equivalencias y/o transformaciones posible con los tipos de Python. A continuación se muestra una tabla de equivalencias:
| JavaScript | Python |
|---|---|
number | int, float |
string | str |
null | None |
object | dict |
array | list |
Tipos no serializables
Algunos tipos nativos de Python son no serializables y deberán ser convertidos a una estructura equivalente antes de poder representarlos como tipos soportados. Por ejemplo, si quisieramos serializar un set, se producirá un TypeError, con el mensaje: Object of type set is not JSON serializable; pero que podríamos resolver convirtiendo el set en una list.
import json
try:
json.dumps({'a', 'b', 'c'})
except TypeError as e:
print(e)Object of type set is not JSON serializable
Tarea para el lector: investigar que otros tipos de Python son no serializables.
Este formato es muy utilizado para el intercambio de datos entre aplicaciones y servidores, especialmente en la Web, ya que es fácil de parsear y generar en la mayoría de los lenguajes de programación.
{
"nombre": "Juan",
"edad": 30,
"es_estudiante": false,
"cursos": ["Matemáticas", "Física", "Química"],
"direccion": {
"calle": "Calle Falsa 123",
"ciudad": "Springfield",
"pais": "USA"
}
}En la sección de Persistencia de Datos vimos cómo trabajar con archivos JSON en Python utilizando la librería estándar json. Aquí veremos cómo utilizar JSON para organizar registros en un archivo.
2Organizando registros con JSON¶
Una forma común de organizar registros en un archivo JSON es utilizar una lista de objetos, donde cada objeto representa un registro. Por ejemplo, si queremos almacenar una agenda de contactos, podemos utilizar la siguiente estructura:
[
{
"nombre": "Juan",
"telefono": "123456789",
"email": "juan@example.com"
},
{
"nombre": "Ana",
"telefono": "987654321",
"email": "ana@example.com"
}
]Cada objeto en la lista representa un contacto, con campos para el nombre, teléfono y correo electrónico. Podemos agregar, eliminar o modificar contactos simplemente manipulando la lista de objetos.
En Python, podemos trabajar con este archivo JSON utilizando la librería json de la siguiente manera
3Agenda en formato JSON¶
A continuación se muestra cómo definir una clase Agenda que almacena los contactos en un archivo .json, junto con su iterador y ejemplos de uso.
import json
import os
class Agenda:
def __init__(self, archivo):
self._archivo = archivo
# Si el archivo no existe, lo crea con una lista vacía
if not os.path.exists(archivo):
with open(archivo, "w") as f:
json.dump([], f)
# Carga los contactos existentes
with open(archivo, "r") as f:
self._contactos = json.load(f)
def guardar_contacto(self, nombre, telefono="", email=""):
if not nombre:
raise ValueError("El nombre es obligatorio")
contacto = {"nombre": nombre, "telefono": telefono, "email": email}
self._contactos.append(contacto)
with open(self._archivo, "w") as f:
json.dump(self._contactos, f, ensure_ascii=False, indent=2)
def cantidad_registros(self):
return len(self._contactos)
def __iter__(self):
return AgendaIterator(self)Definimos el iterador para la agenda:
class AgendaIterator:
"""Iterador para la agenda de contactos en formato JSON"""
def __init__(self, agenda):
self._agenda = agenda
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index >= len(self._agenda._contactos):
raise StopIteration()
contacto = self._agenda._contactos[self._index]
self._index += 1
return contactoEjemplo de uso:
agenda = Agenda("agenda.json")
agenda.guardar_contacto("Juan", "123456789", "juan@example.com")
agenda.guardar_contacto("Ana", "987654321", "ana@example.com")
agenda.guardar_contacto("Homero", "555-8765", "")
agenda.guardar_contacto("Lisa", "", "lisa.simpson@example.com")
print(f"Cantidad de registros: {agenda.cantidad_registros()}")
for contacto in agenda:
print(contacto)Output
Cantidad de registros: 4
{'nombre': 'Juan', 'telefono': '123456789', 'email': 'juan@example.com'}
{'nombre': 'Ana', 'telefono': '987654321', 'email': 'ana@example.com'}
{'nombre': 'Homero', 'telefono': '555-8765', 'email': ''}
{'nombre': 'Lisa', 'telefono': '', 'email': 'lisa.simpson@example.com'}
Si vemos el contenido del archivo agenda.json, se observa que los datos están guardados en formato de texto legible, seguiendo el estándar JSON
with open("agenda.json", "r") as f:
contenido = f.read()
print(contenido)Output
[
{
"nombre": "Juan",
"telefono": "123456789",
"email": "juan@example.com"
},
{
"nombre": "Ana",
"telefono": "987654321",
"email": "ana@example.com"
},
{
"nombre": "Homero",
"telefono": "555-8765",
"email": ""
},
{
"nombre": "Lisa",
"telefono": "",
"email": "lisa.simpson@example.com"
}
]
La principal ventaja de utilizar JSON para organizar registros es que es un formato ampliamente soportado y fácil de leer y escribir. Además, permite almacenar datos estructurados de manera flexible, ya que los objetos pueden tener diferentes campos y tipos de datos.
3.1JSON y bases de datos de documentos (NoSQL)¶
El formato JSON es la piedra angular de las bases de datos de documentos (como MongoDB), un componente vital en muchos sistemas de recuperación de información modernos Manning et al., 2008. A diferencia de las bases de datos relacionales tradicionales, estas permiten almacenar “objetos” complejos sin un esquema rígido predefinido, facilitando la indexación de datos semi-estructurados.
3.2JSON vs XML en Recuperación de Información¶
Si bien ambos formatos se utilizan para representar datos semi-estructurados, JSON suele preferirse en entornos web debido a:
Menor sobrecarga (overhead): No utiliza etiquetas de cierre repetitivas, lo que reduce el tamaño del archivo y el tiempo de transferencia.
Tipado nativo: Soporta números, booleanos y arrays de forma directa, mientras que en XML todo el contenido es texto por defecto.
Eficiencia de parseo: Generalmente, parsear JSON es más rápido y requiere menos memoria que construir un árbol DOM completo de un archivo XML equivalente.
A continuación se define una agenda general donde solo los campos nombres y apellidos son obligatorios, y donde cada registro puede tener incluso campos diferentes.
class AgendaGeneral:
def __init__(self, archivo):
self._archivo = archivo
if not os.path.exists(archivo):
with open(archivo, "w") as f:
json.dump([], f)
with open(archivo, "r") as f:
self._contactos = json.load(f)
def guardar_contacto(self, **kwargs):
if "nombre" not in kwargs or "apellido" not in kwargs:
raise ValueError("Los campos 'nombre' y 'apellido' son obligatorios")
self._contactos.append(kwargs)
with open(self._archivo, "w") as f:
json.dump(self._contactos, f, ensure_ascii=False, indent=2)
def cantidad_registros(self):
return len(self._contactos)
def __iter__(self):
return AgendaIterator(self)Ejemplo de uso:
from pprint import pprint
agenda = AgendaGeneral("agenda_general.json")
agenda.guardar_contacto(
nombre="Juan",
apellido="Pérez",
telefono="123456789",
email="juan.perez@example.com",
)
agenda.guardar_contacto(
nombre="Ana",
apellido="García",
telefono="987654321",
cumpleaños="1990-01-01"
)
agenda.guardar_contacto(
nombre="Homero",
apellido="Simpson",
direccion={"calle": "742 Evergreen Terrace", "ciudad": "Springfield"},
telefono="555-8765",
)
agenda.guardar_contacto(
nombre="Lisa",
apellido="Simpson",
email="lisa.simpson@example.com",
hobbies=["saxofón", "política"],
)
agenda.guardar_contacto(
nombre="Bart",
apellido="Simpson",
telefono="555-1234",
email="bart.simpson@example.com",
)
for contacto in agenda:
# Imprime nombre y apellido en la primera línea
nombre = contacto.get("nombre", "")
apellido = contacto.get("apellido", "")
print(f"{nombre} {apellido}")
# Función recursiva para imprimir campos
def imprimir_campos(d, indent=4):
for clave, valor in d.items():
if clave in ("nombre", "apellido"):
continue
print(f"{" " * indent}{clave}:", end="")
if isinstance(valor, dict):
print()
imprimir_campos(valor, indent + 4)
elif isinstance(valor, list):
print()
for item in valor:
if isinstance(item, dict):
imprimir_campos(item, indent + 4)
else:
print(f"{" " * (indent + 4)}- {item}")
else:
print(f" {valor}")
imprimir_campos(contacto)
print()Output
Juan Pérez
telefono: 123456789
email: juan.perez@example.com
Ana García
telefono: 987654321
cumpleaños: 1990-01-01
Homero Simpson
direccion:
calle: 742 Evergreen Terrace
ciudad: Springfield
telefono: 555-8765
Lisa Simpson
email: lisa.simpson@example.com
hobbies:
- saxofón
- política
Bart Simpson
telefono: 555-1234
email: bart.simpson@example.com
Archivo agenda_general.json:
with open("agenda_general.json", "r") as f:
contenido = f.read()
print(contenido)
print(f"Cantidad de bytes en el archivo: {len(contenido)}")
print(f"Cantidad de registros: {agenda.cantidad_registros()}")Output
[
{
"nombre": "Juan",
"apellido": "Pérez",
"telefono": "123456789",
"email": "juan.perez@example.com"
},
{
"nombre": "Ana",
"apellido": "García",
"telefono": "987654321",
"cumpleaños": "1990-01-01"
},
{
"nombre": "Homero",
"apellido": "Simpson",
"direccion": {
"calle": "742 Evergreen Terrace",
"ciudad": "Springfield"
},
"telefono": "555-8765"
},
{
"nombre": "Lisa",
"apellido": "Simpson",
"email": "lisa.simpson@example.com",
"hobbies": [
"saxofón",
"política"
]
},
{
"nombre": "Bart",
"apellido": "Simpson",
"telefono": "555-1234",
"email": "bart.simpson@example.com"
}
]
Cantidad de bytes en el archivo: 706
Cantidad de registros: 5
- Manning, C. D., Raghavan, P., & Schütze, H. (2008). Introduction to Information Retrieval. Cambridge University Press. https://nlp.stanford.edu/IR-book/