La normalización de los datos en el front-end

Javascript - 27 de octubre de 2021

¡CUIDADO!

La librería que se utiliza en este artículo Normalizr no está siendo mantenida por falta de recursos. Su creador confirma que si es necesario usar la librería se puede usar sin problemas, ya que es una versión estable.

En caso de que encuentres algún error en la librería puedes crear un fork y mantener esa versión para ti.

¿Qué es la normalización de datos?

La normalización de datos es el proceso para reorganizar los datos de tal manera que sean mucho más fáciles de consultar, sin necesidad de recorrer todos los elementos. Con un ejemplo se explica muchísimo mejor:

// datos sin normalizar

[
  {
    id: 1,
    title: 'Nadie lo sabe',
    price: '20,90 €',
    author: {
      id: 1,
      name: 'Tony',
      surname: 'Gratacós'
    }
  },
  {
    id: 2,
    title: 'Nunca',
    price: '23,65 €',
    author: {
      id: 2,
      name: 'Ken',
      surname: 'Follett'
    }
  },
  {
    id: 3,
    title: 'El umbral de la eternidad',
    price: '23,65 €',
    author: {
      id: 2,
      name: 'Ken',
      surname: 'Follett'
    }
  }
]
// datos normalizados

{
  result: [ 1, 2, 3 ],
  entities: {
    authors: {
      '1': {
        id: 1,
        name: 'Tony',
        surname: 'Gratacós'
      },
      '2': {
        id: 2,
        name: 'Ken',
        surname: 'Follett'
      }
    },
    books: {
      '1': {
        id: 1,
        title: 'Nadie lo sabe',
        price: '20,90 €',
        author: 1
      },
      '2': {
        id: 2,
        title: 'Nunca',
        price: '23,65 €',
        author: 2
      },
      '3': {
        id: 3,
        title: 'El umbral de la eternidad',
        price: '23,65 €',
        author: 2
      }}
  }
}

¿Por qué tendrías que normalizar los datos?

Como ya te habrás dado cuenta en el ejemplo anterior, en una estructura de datos no normalizada nos encontramos con los siguientes problemas:

  • Cada vez que queremos acceder a un elemento tenemos que recorrer el array hasta encontrar el objeto que queremos (coste computacional).
  • Como tenemos datos duplicados es más costoso actualizar los datos y verificar que se ha actualizado en todos los sitios correctamente.
  • Al tener objetos anidados es más complicado actualizar los datos.
  • Otro de los problemas que nos encontramos en el front-end si utilizamos librerías como React o Vue.js sucede cuando actualizamos un dato que está anidado, también se está modificando el padre y se estan haciendo renderizados innecesarios en nuestra app.

Estos problemas se hacen cada vez más graves cuando manejamos grandes cantidades de información. Otro punto a favor de la normalización de los datos es que nuestro código se vuelve mucho más sencillo y más legible. Puedes comprobarlo tu mismo con los siguientes ejemplos.

Imagínate que tenemos que encontrar el libro con id 230, con los datos sin normalizar tendríamos que recorrer los libros hasta encontrar el que queremos, en cambio con los datos normalizados accedemos directamente.

// sin normalizar
const book = books.find(book => book.id === 230)

// normalizado
const book = books[230]

Ahora Ken Follet se cambia el apellido por Ken Fernández, en el caso de no tener los datos normalizados tenemos que recorrer todos los libros y modificamos los libros de Ken Follett. En cambio con los datos normalizados accedemos directamente al apellido del autor.

// sin normalizar
books = books.map(book => {
  if (book.author.id !== '2') return book
  return {
    ...book,
    author: {
      ...book.author,
      surname: 'Fernández'
    }
  }
})

// normalizado
authors[2].surname = 'Fernández'

¡Hey! ¿Dónde vas? Ya sé que te he convencido de que tienes que normalizar los datos de tu proyecto, pero antes de que te vayas a normalizar como loco por ahi déjame enseñarte como lo puedes hacer.

Normalizar datos con Normalizr

Hay muchas maneras de normalizar los datos, incluso te puedes crear una función que te lo haga, pero existe una librería muy completa para esto, Normalizr.

Lo primero que hay que hacer es definir el schema de los datos, en el ejemplo que estamos utilizando en este post sería algo así:

// Definición de la entidad authors
const authorsSchema = new schema.Entity('authors');

// Definición de la entidad books
const bookSchema = new schema.Entity('books', {
  author: authorsSchema
});

Por defecto al crear nuestra entidad cogerá el atributo id , para cambiar esto simplemente tenemos que pasarle el idAttribute. El idAttribute es una función que devuelve el valor del id.

const bookSchema = new schema.Entity('books',
  {
    author: authorsSchema
  },
  {
    idAttribute: (book) => book.code
  }
);

Una vez ya tenemos nuestros schemas solo nos queda llamar a normalize(data, schema) para normalizar nuestros datos.

const { result, entities } = normalize(rawBooks, [bookSchema]);

Ale ya tienes tu objeto normalizado 🥳 Te dejo el ejemplo completo:

const { schema, normalize } = require('normalizr');

// Definición de la entidad authors
const authorsSchema = new schema.Entity('authors');

// Definición de la entidad books
const bookSchema = new schema.Entity('books', {
  author: authorsSchema
});

const rawBooks = [
    {
      id: 1,
      title: 'Nadie lo sabe',
      price: '20,90 €',
      author: {
        id: 1,
        name: 'Tony',
        surname: 'Gratacós'
      }
    },
    {
      id: 2,
      title: 'Nunca',
      price: '23,65 €',
      author: {
        id: 2,
        name: 'Ken',
        surname: 'Follett'
      }
    },
    {
      id: 3,
      title: 'El umbral de la eternidad',
      price: '23,65 €',
      author: {
        id: 2,
        name: 'Ken',
        surname: 'Follett'
      }
    }
  ]

const { result, entities } = normalize(rawBooks, [bookSchema]);

console.log(entities.books[1].title) // output: "Nadie lo sabe"
console.log(entities.authors[2].name) // output: "Ken"

Denormalización (extra)

Es posible que una vez modificados los datos necesites volver a la estructura inicial para enviar los datos al back end. Normalizr tiene el método denormalize, que te devolverá los datos modificados con la estructura inicial.

const resultDenormalize = denormalize(result, [bookSchema], entities)

¡Ojo! Denormalización objetos anidados de gran tamaño en el front end puede causar problemas de rendimiento. Utilizado únicamente cuando sea necesario.

Conclusión

Normalizar los datos en nuestro front end es una buena práctica a la hora de manejar grandes cantidades de datos. Te recomiendo a que visites la documentación de Normalizr y para que veas todo lo que puedes hacer con esta librería.