JavaScript (JS) -- JSON method, custom toJSON, transform JSON format

Posted by esconsult1 on Wed, 23 Feb 2022 17:30:02 +0100

JSON method, toJSON

Suppose we have a complex object that we want to convert into a string to send over the network, or just to output it in the log.

JSON.stringify

JSON (JavaScript Object Notation) is a common format for representing values and objects. stay RFC 4627 It is described in the standard. It was originally created for JavaScript, but many other programming languages also have libraries for working with it. Therefore, when the client side uses JavaScript and the server side is written in languages such as Ruby/PHP/Java, it is easy to exchange data with JSON.

JavaScript provides the following methods:

  • JSON.stringify converts an object to JSON.
  • JSON.parse converts JSON back to an object.

For example, here we use JSON Stringify a student object:

let student = {
  name: 'John',
  age: 30,
  isAdmin: false,
  courses: ['html', 'css', 'js'],
  wife: null
};

let json = JSON.stringify(student);

alert(typeof json); // we've got a string!

alert(json);
/* JSON Coded object:
{
  "name": "John",
  "age": 30,
  "isAdmin": false,
  "courses": ["html", "css", "js"],
  "wife": null
}
*/

Method JSON Stringify (student) receives the object and converts it to a string.

The resulting JSON string is an object called JSON encoded or serialized or stringed or marshaled. We are now ready to send it by wire or put it into ordinary data storage.

Please note that there are several important differences between JSON encoded objects and object literals:

  • Use double quotation marks for strings. There are no single quotes or backquotes in JSON. So 'John' is converted to "John".
  • Object attribute names are also double quoted. This is mandatory. So age:30 is converted to "age":30.

JSON.stringify can also be applied to primitive data types.

JSON supports the following data types:

  • Objects { ... }
  • Arrays [ ... ]
  • Primitives:
    • strings,
    • numbers,
    • boolean values true/false,
    • null.

For example:

// Are numbers in JSON or numbers
alert( JSON.stringify(1) ) // 1

// The string is still a string in JSON, which is only enclosed by double quotation marks
alert( JSON.stringify('test') ) // "test"

alert( JSON.stringify(true) ); // true

alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]

JSON is a language independent pure data specification, so some JavaScript specific object properties will be defined by JSON Stringify skip.

Namely:

  • Function properties (Methods).
  • Key and value of Symbol type.
  • Store undefined attributes.
let user = {
  sayHi() { // Ignored
    alert("Hello");
  },
  [Symbol("id")]: 123, // Ignored
  something: undefined // Ignored
};

alert( JSON.stringify(user) ); // {} (empty object)

Usually it's good. If this is not the way we want, we will soon see how to customize the conversion method.

The best thing is that nested object conversion is supported and can be converted automatically.

For example:

let meetup = {
  title: "Conference",
  room: {
    number: 23,
    participants: ["john", "ann"]
  }
};

alert( JSON.stringify(meetup) );
/* The whole deconstruction is stringed
{
  "title":"Conference",
  "room":{"number":23,"participants":["john","ann"]},
}
*/

Important limitation: no circular references.

For example:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: ["john", "ann"]
};

meetup.place = room;       // meetup refers to room
room.occupiedBy = meetup; // room refers to meetup

JSON.stringify(meetup); // Error: Converting circular structure to JSON

Here, the conversion fails because of the circular reference: room Occupiedby refers to meetup, meetup Place refers to room:

Exclusion and conversion: replace

JSON. The complete syntax of stringify is:

let json = JSON.stringify(value[, replacer, space])
  • value

    The value to encode.

  • replacer

    The attribute array or mapping function(key, value) to be encoded.

  • space

    Number of spaces used for formatting

In most cases, JSON Stringify is used only with the first parameter. However, if we need to fine tune the replacement process, such as filtering out circular references, we can use JSON The second parameter of stringify.

If we pass an array of attributes to it, only these attributes will be encoded.

For example:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup refers to room
};

room.occupiedBy = meetup; // room refers to meetup

alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}

We may be too strict here. The attribute list is applied to the entire object structure. So participants is empty because name is not in the list.

Let's include room. In addition to the one that causes a circular reference All attributes except occupiedby:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup refers to room
};

room.occupiedBy = meetup; // room refers to meetup

alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
  "title":"Conference",
  "participants":[{"name":"John"},{"name":"Alice"}],
  "place":{"number":23}
}
*/

Now everything except occupiedBy is serialized. But the list of attributes is too long.

Fortunately, we can use a function instead of an array as a replacer.

This function will be called for each (key,value) pair and return the "replaced" value, which will replace the original value. undefined if the value is skipped.

In our example, we can return value as is for all contents except occupiedBy. To occupiedBy, the following code returns undefined:

let room = {
  number: 23
};

let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room // meetup refers to room
};

room.occupiedBy = meetup; // room refers to meetup

alert( JSON.stringify(meetup, function replacer(key, value) {
  alert(`${key}: ${value}`);
  return (key == 'occupiedBy') ? undefined : value;
}));

/* key:value pairs that come to 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]
*/

Note that the replace function takes each key / value pair, including nested objects and array items. It is applied recursively. The value of this in replace is the object that contains the current property.

The first call is special. It is made with a special "wrapper object": {"": meetup}. In other words, the key of the first (key, value) pair is empty, and the value is the whole target object. This is why the first line in the above example is ": [object]".

The idea is to provide the replacer with as many functions as possible: if necessary, it has the opportunity to analyze and replace / skip the entire object.

Formatting: space

JSON. The third parameter of stringify (value, replace, spaces) is the number of spaces used to optimize the format.

Previously, all stringed objects had no indents or extra spaces. If we want to send an object over the network, it's no problem. The space parameter is specifically used to adjust for a more beautiful output.

space = 2 here tells JavaScript to display nested objects in multiple lines, with 2 spaces indented inside the object:

let user = {
  name: "John",
  age: 25,
  roles: {
    isAdmin: false,
    isEditor: true
  }
};

alert(JSON.stringify(user, null, 2));
/* Indentation of two spaces:
{
  "name": "John",
  "age": 25,
  "roles": {
    "isAdmin": false,
    "isEditor": true
  }
}
*/

/* For JSON The result of stringify (user, null, 4) will be indented more:
{
    "name": "John",
    "age": 25,
    "roles": {
        "isAdmin": false,
        "isEditor": true
    }
}
*/

The third parameter can also be a string. In this case, the string is used for indentation, not the number of spaces.

The spaces parameter is only used for logging and beautifying output.

Customize "toJSON"

Like toString for string conversion, objects can also provide toJSON methods for JSON conversion. If available, JSON Stringify will call it automatically.

Let's add a custom JSON object to the room:

let room = {
  number: 23,
  toJSON() {
    return this.number;
  }
};

let meetup = {
  title: "Conference",
  room
};

alert( JSON.stringify(room) ); // 23

alert( JSON.stringify(meetup) );
/*
  {
    "title":"Conference",
    "room": 23
  }
*/

As we can see, toJSON can be used to call JSON directly Stringify (room) can also be used when room is nested in another encoding object.

JSON.parse

To decode JSON strings, we need another method JSON.parse.

Syntax:

let value = JSON.parse(str, [reviver]);
  • str

    JSON string to parse.

  • reviver

    Optional function(key,value), which will be called for each (key, value) pair and can convert the value.

For example:

// String array
let numbers = "[0, 1, 2, 3]";

numbers = JSON.parse(numbers);

alert( numbers[1] ); // 1

For nested objects:

let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';

let user = JSON.parse(userData);

alert( user.friends[1] ); // 1

JSON can be very complex, and objects and arrays can contain other objects and arrays. But they must follow the same JSON format.

The following are typical mistakes when writing JSON by hand (sometimes we have to write it for debugging purposes):

let json = `{
  name: "John",                     // Error: attribute name does not have double quotes
  "surname": 'Smith',               // Error: value is in single quotation marks (double quotation marks must be used)
  'isAdmin': false                  // Error: the key uses single quotation marks (double quotation marks must be used)
  "birthday": new Date(2000, 2, 3), // Error: "new" is not allowed, it can only be a bare value
  "friends": [0,1,2,3]              // This is no problem
}`;

In addition, JSON does not support annotations. Adding comments to JSON is not valid.

There is another kind called JSON5 It allows unquoted keys, comments, etc. But this is an independent library, not in the language specification.

The regular JSON format is strict, not because its developers are lazy, but to realize simple, reliable and fast parsing algorithms.

Using reviver

Imagine that we get a stringed meetup object from the server.

It looks like this:

// title: (meetup title), date: (meetup date)
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

... now we need to deserialize it and convert it back to JavaScript objects.

Let's call JSON Parse to complete:

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str);

alert( meetup.date.getDate() ); // Error!

Ah! Wrong report!

meetup. The value of Date is a string, not a Date object. JSON. How does parse know that a string should be converted to a Date?

Let's pass the reviver function to JSON Parse as the second parameter, the function returns all values "as is", but Date will become Date:

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';

let meetup = JSON.parse(str, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( meetup.date.getDate() ); // Now it works normally!

By the way, this also applies to nested objects:

let schedule = `{
  "meetups": [
    {"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
    {"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
  ]
}`;

schedule = JSON.parse(schedule, function(key, value) {
  if (key == 'date') return new Date(value);
  return value;
});

alert( schedule.meetups[1].date.getDate() ); // Normal operation!

summary

  • JSON is a data format with its own independent standards and libraries for most programming languages.
  • JSON supports object, array, string, number, boolean and null.
  • JavaScript provides a way to serialize into JSON JSON.stringify And methods of parsing JSON JSON.parse.
  • Both methods support conversion functions for intelligent read / write.
  • If an object has toJSON, it will be JSON Stringify call.

Topics: Javascript Front-end JSON