Métodos JSON, toJSON
Supongamos que tenemos un objeto complejo y queremos convertirlo en una cadena de texto para enviarlo por la red o simplemente para visualizarlo para fines de registro.
Naturalmente, dicha cadena debe incluir todas las propiedades importantes.
Podríamos implementar la conversión de esta manera:
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${this.name}", age: ${this.age}}`;
}
};
alert(user); // {name: "John", age: 30}
Sin embargo, a medida que desarrollamos el código, se pueden añadir nuevas propiedades, o renombrar y eliminar otras. Actualizar el método toString cada vez que esto ocurre puede volverse tedioso. Podríamos intentar recorrer las propiedades, pero ¿qué pasa si el objeto es complejo y tiene objetos anidados? Necesitaríamos implementar su conversión también.
Afortunadamente, no es necesario escribir todo el código para manejar esto. Esta tarea ya ha sido resuelta.
JSON.stringify
JSON (JavaScript Object Notation) es un formato universal para representar valores y objetos. Es un estándar definido en el RFC 4627. Originalmente fue creado para JavaScript, pero muchos otros lenguajes también tienen librerías para manejarlo. Por lo tanto, es fácil usar JSON para intercambiar información entre un cliente que utiliza JavaScript y un servidor escrito en Ruby, PHP, Java, etc.
JavaScript proporciona dos métodos:
JSON.stringify
para convertir objetos a JSON.JSON.parse
para convertir JSON de vuelta a un objeto.
Por ejemplo, aquí usamos JSON.stringify
en un objeto student
:
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
spouse: null
};
let json = JSON.stringify(student);
alert(typeof json); // ¡obtenemos una cadena de texto!
alert(json);
/* Objeto codificado en JSON:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"spouse": null
}
*/
El método JSON.stringify(student)
toma el objeto y lo convierte en una cadena de texto.
La cadena JSON resultante se denomina objeto JSON codificado o serializado. Estamos listos para enviarlo por la red o almacenarlo.
Ten en cuenta que el objeto JSON codificado tiene varias diferencias importantes con el objeto literal:
- Las cadenas utilizan comillas dobles. No hay comillas simples o acentos en JSON. Por lo tanto,
'John'
se convierte en"John"
. - Los nombres de las propiedades del objeto también llevan comillas dobles. Esto es obligatorio. Así,
age:30
se convierte en"age":30
.
JSON.stringify
también se puede aplicar a tipos de datos primitivos.
JSON soporta los siguientes tipos de datos:
- Objetos
{ ... }
- Arreglos
[ ... ]
- Primitivos:
- Cadenas de texto,
- Números,
- Valores booleanos
true/false
, null
.
Por ejemplo:
// un número en JSON es sólo un número
alert( JSON.stringify(1) ); // 1
// una cadena en JSON sigue siendo una cadena de texto, pero con comillas dobles
alert( JSON.stringify('test') ); // "test"
alert( JSON.stringify(true) ); // true
alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON es una especificación de datos independiente del lenguaje, por lo que algunas propiedades específicas de JavaScript se omiten al usar JSON.stringify
.
Estas son:
- Propiedades de funciones (métodos).
- Propiedades simbólicas.
- Propiedades que contienen
undefined
.
let user = {
sayHi() { // ignorado
alert("Hello");
},
[Symbol("id")]: 123, // ignorado
something: undefined // ignorado
};
alert( JSON.stringify(user) ); // {} (objeto vacío)
Normalmente esto está bien. Si no es lo que queremos, pronto veremos cómo personalizar el proceso.
Lo mejor es que se permiten objetos anidados y se convierten automáticamente.
Por ejemplo:
let meetup = {
title: "Conference",
room: {
number: 23,
participants: ["john", "ann"]
}
};
alert( JSON.stringify(meetup) );
/* La estructura completa se convierte en cadena:
{
"title":"Conference",
"room":{"number":23,"participants":["john","ann"]}
}
*/
Una limitación importante: no deben existir referencias circulares.
Por ejemplo:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
meetup.place = room; // meetup hace referencia a room
room.occupiedBy = meetup; // room hace referencia a meetup
JSON.stringify(meetup); // Error: Convirtiendo estructura circular a JSON
Aquí, la conversión falla debido a una referencia circular: room.occupiedBy
hace referencia a meetup
, y meetup.place
hace referencia a room
.
Excluyendo y transformando: replacer
La sintaxis completa de JSON.stringify
es:
let json = JSON.stringify(value[, replacer, space])
value
: Un valor para codificar.replacer
: Un array de propiedades para codificar o una función de mapeofunction(propiedad, valor)
.space
: La cantidad de espacio para usar para el formateo.
La mayor parte del tiempo, JSON.stringify
se usa solo con el primer argumento. Pero si necesitamos ajustar el proceso de sustitución, como para filtrar las referencias circulares, podemos usar el segundo argumento de JSON.stringify
.
Si pasamos un array de propiedades a él, solo esas propiedades serán codificadas.
Por ejemplo:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup hace referencia a room
};
room.occupiedBy = meetup; // room hace referencia a meetup
alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
Aquí probablemente seamos demasiado estrictos. La lista de propiedades se aplica a toda la estructura del objeto. Por lo tanto, los objetos en participants
están vacíos porque name
no está en la lista.
Incluyamos en la lista todas las propiedades excepto room.occupiedBy
para evitar la referencia circular:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup hace referencia a room
};
room.occupiedBy = meetup; // room hace referencia a meetup
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
Ahora todo, excepto occupiedBy
, está serializado. Pero la lista de propiedades es bastante larga.
Afortunadamente, podemos usar una función en lugar de un array como replacer
.
La función se llamará para cada par de (propiedad, valor)
y debe devolver el valor “sustituido”, que se utilizará en lugar del original, o undefined
si el valor va a ser omitido.
En nuestro caso, podemos devolver value
“tal cual” para todo excepto occupiedBy
. Para ignorar occupiedBy
, el código de abajo devuelve undefined
:
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup hace referencia a room
};
room.occupiedBy = meetup; // room hace referencia a meetup
alert( JSON.stringify(meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy') ? undefined : value;
}));
/* pares de propiedad:valor que llegan a replacer:
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
occupiedBy: [object Object]
*/
Ten en cuenta que la función replacer
recibe todos los pares de propiedad/valor, incluyendo objetos anidados y elementos de array. Se aplica recursivamente. El valor de this
dentro de replacer
es el objeto que contiene la propiedad actual.
El primer llamado es especial. Se realiza utilizando un “Objeto contenedor” especial: {"": meetup}
. En otras palabras, el primer par (propiedad, valor)
tiene una propiedad vacía, y el valor es el objeto objetivo como un todo. Por esto, la primera línea es ":[object Object]"
en el ejemplo anterior.
La idea es proporcionar tanta capacidad para replacer
como sea posible: tiene una oportunidad de analizar y reemplazar/omitir incluso el objeto completo si es necesario.
Formateo: space
El tercer argumento de JSON.stringify(value[, replacer, space])
es el número de espacios de sangría para formateo.
Anteriormente, todos los resultados de JSON.stringify
eran de una línea. Eso es bueno si queremos enviar un objeto sobre la red. Pero para logueo y formateo de salida, es más conveniente que se vea bien.
Eso es fácil de hacer con el argumento space
:
let user = {
name: "John",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
alert(JSON.stringify(user, null, 2));
/* dos espacios de sangría:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
alert(JSON.stringify(user, null, 4));
/* cuatro espacios de sangría:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
La llamada JSON.stringify(user, null, 4)
tiene más espacio en la salida.
Objeto.toJSON
Como alternativa al replacer
, podemos proporcionar un método toJSON
para todo el objeto o para una propiedad específica.
Cuando JSON.stringify
llama a este método, debe devolver un valor que se usará en lugar del objeto original.
Por ejemplo:
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
room
};
alert( JSON.stringify(room) ); // 23
alert( JSON.stringify(meetup) );
/* El objeto meetup se convierte a:
{
"title":"Conference",
"room": 23
}
*/
Como puedes ver, toJSON
es llamado tanto para el objeto directo JSON.stringify(room)
como para los objetos anidados.
Aquí la estructura de datos es simple, pero puede ser más compleja. De cualquier manera, debemos devolver un valor “plano” para la representación JSON.
Recapitulación
JSON.stringify
convierte objetos en cadenas JSON para enviarlas a través de la red u otros propósitos.JSON.parse
convierte las cadenas JSON de nuevo en objetos.- Ambos métodos pueden aplicar transformaciones y controlar el proceso de conversión.