← Volver al Blog

Estructuras JSON Avanzadas: Patrones y Técnicas Profesionales

Domina patrones avanzados de JSON: normalización, grafos, versionado, polimorfismo y arquitecturas escalables.

Big JSON Team15 min de lecturaAvanzado
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

15 min lectura

# Estructuras JSON Avanzadas: Patrones y Técnicas Profesionales

Más allá de objetos y arrays simples, JSON puede modelar estructuras complejas. Esta guía avanzada explora patrones, técnicas y arquitecturas para JSON profesional.

Normalización de Datos

Problema: Datos Duplicados

{

"posts": [

{

"id": 1,

"titulo": "Intro a JSON",

"autor": {

"id": 101,

"nombre": "Ana García",

"email": "ana@ejemplo.com"

},

"comentarios": [

{

"id": 1,

"texto": "Excelente post",

"autor": {

"id": 102,

"nombre": "Carlos López",

"email": "carlos@ejemplo.com"

}

}

]

},

{

"id": 2,

"titulo": "JSON Avanzado",

"autor": {

"id": 101,

"nombre": "Ana García",

"email": "ana@ejemplo.com"

}

}

]

}

Problemas:
  • Duplicación de datos del autor
  • Difícil mantener consistencia
  • Mayor tamaño de archivo

Solución: Normalización

{

"entidades": {

"posts": {

"1": {

"id": 1,

"titulo": "Intro a JSON",

"autorId": 101,

"comentarioIds": [1]

},

"2": {

"id": 2,

"titulo": "JSON Avanzado",

"autorId": 101,

"comentarioIds": []

}

},

"usuarios": {

"101": {

"id": 101,

"nombre": "Ana García",

"email": "ana@ejemplo.com"

},

"102": {

"id": 102,

"nombre": "Carlos López",

"email": "carlos@ejemplo.com"

}

},

"comentarios": {

"1": {

"id": 1,

"texto": "Excelente post",

"autorId": 102,

"postId": 1

}

}

},

"resultado": [1, 2]

}

Ventajas:
  • ✅ Datos únicos sin duplicación
  • ✅ Actualizaciones fáciles
  • ✅ Menor tamaño
  • ✅ Acceso O(1) por ID

Normalizar con Código

function normalizar(data, schema) {

const entidades = {};

const resultado = [];

function procesarItem(item, tipo) {

if (!entidades[tipo]) {

entidades[tipo] = {};

}

const id = item.id;

const itemNormalizado = { ...item };

// Normalizar relaciones

Object.keys(schema[tipo] || {}).forEach(relacion => {

const config = schema[tipo][relacion];

if (Array.isArray(itemNormalizado[relacion])) {

itemNormalizado[${relacion}Ids] = itemNormalizado[relacion].map(

subItem => {

procesarItem(subItem, config.tipo);

return subItem.id;

}

);

delete itemNormalizado[relacion];

} else if (itemNormalizado[relacion]) {

procesarItem(itemNormalizado[relacion], config.tipo);

itemNormalizado[${relacion}Id] = itemNormalizado[relacion].id;

delete itemNormalizado[relacion];

}

});

entidades[tipo][id] = itemNormalizado;

return id;

}

data.forEach(item => {

const id = procesarItem(item, 'posts');

resultado.push(id);

});

return { entidades, resultado };

}

// Schema

const schema = {

posts: {

autor: { tipo: 'usuarios' },

comentarios: { tipo: 'comentarios' }

},

comentarios: {

autor: { tipo: 'usuarios' }

}

};

// Uso

const datosNormalizados = normalizar(posts, schema);

Desnormalizar para Uso

function desnormalizar(entidades, resultado, tipo) {

return resultado.map(id => {

const item = { ...entidades[tipo][id] };

// Reconstruir relaciones

Object.keys(item).forEach(key => {

if (key.endsWith('Id')) {

const relacion = key.slice(0, -2);

const tipoRelacion = inferirTipo(relacion);

const relacionId = item[key];

item[relacion] = entidades[tipoRelacion][relacionId];

delete item[key];

} else if (key.endsWith('Ids')) {

const relacion = key.slice(0, -3);

const tipoRelacion = inferirTipo(relacion);

item[relacion] = item[key].map(

relacionId => entidades[tipoRelacion][relacionId]

);

delete item[key];

}

});

return item;

});

}

Grafos en JSON

Representación de Árbol

{

"id": "raiz",

"nombre": "Sistema de Archivos",

"tipo": "directorio",

"hijos": [

{

"id": "docs",

"nombre": "Documentos",

"tipo": "directorio",

"hijos": [

{

"id": "readme",

"nombre": "README.md",

"tipo": "archivo",

"tamano": 1024

}

]

},

{

"id": "src",

"nombre": "src",

"tipo": "directorio",

"hijos": []

}

]

}

Grafo con Adyacencias

{

"nodos": [

{ "id": "A", "nombre": "Madrid", "tipo": "ciudad" },

{ "id": "B", "nombre": "Barcelona", "tipo": "ciudad" },

{ "id": "C", "nombre": "Valencia", "tipo": "ciudad" }

],

"aristas": [

{ "origen": "A", "destino": "B", "distancia": 621, "tipo": "carretera" },

{ "origen": "B", "destino": "C", "distancia": 349, "tipo": "carretera" },

{ "origen": "A", "destino": "C", "distancia": 352, "tipo": "carretera" }

]

}

Lista de Adyacencia

{

"grafo": {

"A": {

"nombre": "Madrid",

"conexiones": [

{ "nodo": "B", "peso": 621 },

{ "nodo": "C", "peso": 352 }

]

},

"B": {

"nombre": "Barcelona",

"conexiones": [

{ "nodo": "A", "peso": 621 },

{ "nodo": "C", "peso": 349 }

]

},

"C": {

"nombre": "Valencia",

"conexiones": [

{ "nodo": "B", "peso": 349 },

{ "nodo": "A", "peso": 352 }

]

}

}

}

Algoritmo de Búsqueda en Grafo

class Grafo {

constructor(datos) {

this.nodos = new Map();

// Construir grafo

datos.nodos.forEach(nodo => {

this.nodos.set(nodo.id, { ...nodo, conexiones: [] });

});

datos.aristas.forEach(arista => {

this.nodos.get(arista.origen).conexiones.push({

destino: arista.destino,

peso: arista.distancia

});

});

}

// Búsqueda en amplitud (BFS)

bfs(inicio, objetivo) {

const cola = [inicio];

const visitados = new Set([inicio]);

const padres = new Map();

while (cola.length > 0) {

const actual = cola.shift();

if (actual === objetivo) {

return this.reconstruirCamino(padres, inicio, objetivo);

}

const nodo = this.nodos.get(actual);

nodo.conexiones.forEach(({ destino }) => {

if (!visitados.has(destino)) {

visitados.add(destino);

padres.set(destino, actual);

cola.push(destino);

}

});

}

return null;

}

reconstruirCamino(padres, inicio, objetivo) {

const camino = [objetivo];

let actual = objetivo;

while (actual !== inicio) {

actual = padres.get(actual);

camino.unshift(actual);

}

return camino;

}

}

// Uso

const grafo = new Grafo(datosGrafo);

const camino = grafo.bfs('A', 'C');

console.log(camino); // ['A', 'C']

Polimorfismo en JSON

Discriminador de Tipo

{

"figuras": [

{

"tipo": "circulo",

"radio": 5,

"centro": { "x": 0, "y": 0 }

},

{

"tipo": "rectangulo",

"ancho": 10,

"alto": 5,

"esquina": { "x": 0, "y": 0 }

},

{

"tipo": "triangulo",

"vertices": [

{ "x": 0, "y": 0 },

{ "x": 5, "y": 0 },

{ "x": 2.5, "y": 5 }

]

}

]

}

Procesamiento Polimórfico

class Figura {

static crear(datos) {

switch (datos.tipo) {

case 'circulo':

return new Circulo(datos);

case 'rectangulo':

return new Rectangulo(datos);

case 'triangulo':

return new Triangulo(datos);

default:

throw new Error(Tipo desconocido: ${datos.tipo});

}

}

}

class Circulo {

constructor({ radio, centro }) {

this.radio = radio;

this.centro = centro;

}

area() {

return Math.PI this.radio 2;

}

}

class Rectangulo {

constructor({ ancho, alto }) {

this.ancho = ancho;

this.alto = alto;

}

area() {

return this.ancho this.alto;

}

}

// Procesar

const figuras = datosJSON.figuras.map(f => Figura.crear(f));

const areas = figuras.map(f => f.area());

Versionado de Datos

Estrategia 1: Campo de Versión

{

"version": "2.0",

"datos": {

"usuario": {

"id": "123",

"email": "usuario@ejemplo.com",

"perfil": {

"nombre": "Ana García",

"preferencias": {

"idioma": "es",

"tema": "oscuro"

}

}

}

}

}

Migrar Versiones

const migraciones = {

'1.0': (datos) => {

// V1.0 → V1.1: Agregar campo email

return {

...datos,

version: '1.1',

email: datos.contacto.email

};

},

'1.1': (datos) => {

// V1.1 → V2.0: Reestructurar perfil

return {

version: '2.0',

datos: {

usuario: {

id: datos.id,

email: datos.email,

perfil: {

nombre: datos.nombre,

preferencias: datos.configuracion

}

}

}

};

}

};

function migrar(datos, versionObjetivo) {

let version = datos.version || '1.0';

let datosMigrados = { ...datos };

while (version !== versionObjetivo) {

if (!migraciones[version]) {

throw new Error(No hay migración para versión ${version});

}

datosMigrados = migracionesversion;

version = datosMigrados.version;

}

return datosMigrados;

}

Estrategia 2: Versionado en URL

/api/v1/usuarios    → Versión 1

/api/v2/usuarios → Versión 2

Paginación Eficiente

Cursor-Based Pagination

{

"datos": [

{ "id": "user_101", "nombre": "Ana" },

{ "id": "user_102", "nombre": "Carlos" },

{ "id": "user_103", "nombre": "María" }

],

"paginacion": {

"siguiente": "user_103",

"anterior": null,

"tieneSiguiente": true,

"tieneAnterior": false

}

}

Implementación:
async function obtenerUsuarios(cursor = null, limite = 20) {

let query = db.usuarios

.orderBy('id')

.limit(limite + 1);

if (cursor) {

query = query.startAfter(cursor);

}

const resultados = await query.get();

const tieneSiguiente = resultados.length > limite;

const datos = resultados.slice(0, limite);

const ultimoId = datos[datos.length - 1]?.id;

return {

datos,

paginacion: {

siguiente: tieneSiguiente ? ultimoId : null,

anterior: cursor,

tieneSiguiente,

tieneAnterior: !!cursor

}

};

}

Offset-Based Pagination

{

"datos": [...],

"paginacion": {

"pagina": 2,

"porPagina": 20,

"total": 1534,

"totalPaginas": 77

}

}

HAL (Hypertext Application Language)

Estructura HAL

{

"_links": {

"self": { "href": "/api/usuarios/123" },

"posts": { "href": "/api/usuarios/123/posts" },

"amigos": { "href": "/api/usuarios/123/amigos" }

},

"id": "123",

"nombre": "Ana García",

"email": "ana@ejemplo.com",

"_embedded": {

"posts": [

{

"_links": {

"self": { "href": "/api/posts/1" }

},

"id": "1",

"titulo": "Mi primer post"

}

]

}

}

Ventajas:
  • ✅ Auto-documentación
  • ✅ Descubrimiento de API
  • ✅ Navegación hipermedia

JSON-LD (Linked Data)

Contexto Semántico

{

"@context": "https://schema.org",

"@type": "Person",

"name": "Ana García",

"email": "ana@ejemplo.com",

"address": {

"@type": "PostalAddress",

"streetAddress": "Calle Mayor 123",

"addressLocality": "Madrid",

"addressCountry": "ES"

},

"worksFor": {

"@type": "Organization",

"name": "TechCorp",

"url": "https://techcorp.ejemplo.com"

}

}

Uso:
  • SEO mejorado
  • Web semántica
  • Interoperabilidad de datos

Event Sourcing con JSON

Estructura de Eventos

{

"eventos": [

{

"id": "evt_001",

"tipo": "UsuarioCreado",

"timestamp": "2024-01-15T10:00:00Z",

"agregadoId": "user_123",

"datos": {

"nombre": "Ana García",

"email": "ana@ejemplo.com"

}

},

{

"id": "evt_002",

"tipo": "EmailActualizado",

"timestamp": "2024-01-16T14:30:00Z",

"agregadoId": "user_123",

"datos": {

"emailAnterior": "ana@ejemplo.com",

"emailNuevo": "ana.garcia@ejemplo.com"

}

},

{

"id": "evt_003",

"tipo": "UsuarioDesactivado",

"timestamp": "2024-01-20T09:00:00Z",

"agregadoId": "user_123",

"datos": {

"razon": "Solicitud del usuario"

}

}

]

}

Reconstruir Estado

class Usuario {

constructor() {

this.id = null;

this.nombre = null;

this.email = null;

this.activo = false;

}

aplicarEvento(evento) {

switch (evento.tipo) {

case 'UsuarioCreado':

this.id = evento.agregadoId;

this.nombre = evento.datos.nombre;

this.email = evento.datos.email;

this.activo = true;

break;

case 'EmailActualizado':

this.email = evento.datos.emailNuevo;

break;

case 'UsuarioDesactivado':

this.activo = false;

break;

}

}

static reconstruirDesdeEventos(eventos) {

const usuario = new Usuario();

eventos.forEach(evento => usuario.aplicarEvento(evento));

return usuario;

}

}

// Uso

const usuario = Usuario.reconstruirDesdeEventos(

eventos.filter(e => e.agregadoId === 'user_123')

);

CQRS con JSON

Comandos

{

"comando": "CrearPedido",

"id": "cmd_001",

"timestamp": "2024-01-15T10:00:00Z",

"datos": {

"clienteId": "user_123",

"items": [

{ "productoId": "prod_456", "cantidad": 2 },

{ "productoId": "prod_789", "cantidad": 1 }

],

"direccionEnvio": {

"calle": "Calle Mayor 123",

"ciudad": "Madrid"

}

}

}

Consultas (Proyecciones)

{

"pedidosCliente": {

"clienteId": "user_123",

"pedidos": [

{

"id": "order_001",

"fecha": "2024-01-15",

"total": 129.99,

"estado": "enviado",

"itemCount": 3

},

{

"id": "order_002",

"fecha": "2024-02-01",

"total": 49.99,

"estado": "procesando",

"itemCount": 1

}

],

"totalGastado": 179.98,

"pedidosActivos": 1

}

}

Compresión y Optimización

Delta Compression

{

"base": {

"id": "doc_123",

"titulo": "Documento Original",

"contenido": "Lorem ipsum...",

"version": 1

},

"deltas": [

{

"version": 2,

"cambios": [

{ "op": "replace", "path": "/titulo", "value": "Documento Actualizado" }

]

},

{

"version": 3,

"cambios": [

{ "op": "add", "path": "/tags", "value": ["importante"] }

]

}

]

}

JSON Patch (RFC 6902)

[

{ "op": "add", "path": "/nuevo", "value": "valor" },

{ "op": "remove", "path": "/antiguo" },

{ "op": "replace", "path": "/existente", "value": "nuevoValor" },

{ "op": "move", "from": "/origen", "path": "/destino" },

{ "op": "copy", "from": "/fuente", "path": "/copia" },

{ "op": "test", "path": "/campo", "value": "valorEsperado" }

]

Mejores Prácticas

1. Diseño Consistente

{

"meta": {

"version": "1.0",

"timestamp": "2024-01-15T10:00:00Z",

"requestId": "req_123"

},

"data": {

// Datos de respuesta

},

"errors": [],

"links": {

"self": "/api/recurso",

"next": "/api/recurso?page=2"

}

}

2. Validación con JSON Schema

{

"$schema": "http://json-schema.org/draft-07/schema#",

"type": "object",

"properties": {

"usuario": {

"type": "object",

"properties": {

"id": { "type": "string", "pattern": "^user_[0-9]+$" },

"email": { "type": "string", "format": "email" }

},

"required": ["id", "email"]

}

}

}

3. Documentación

Usa OpenAPI/Swagger para documentar estructuras JSON de APIs.

Conclusión

Las estructuras JSON avanzadas permiten modelar sistemas complejos eficientemente. Aplica estos patrones según las necesidades de tu aplicación.

Puntos Clave

  • Normaliza datos para evitar duplicación
  • Usa discriminadores para polimorfismo
  • Implementa versionado desde el principio
  • Considera HAL o JSON-LD para APIs RESTful
  • Event Sourcing para auditoría completa
  • Valida con JSON Schema

¡Domina JSON avanzado para arquitecturas escalables!

Share:

Artículos Relacionados

Read in English