Some features of JSON.stringify
Some common occasions:
1. Store JSON object s in localStorage;
2. JSON body in POST request
3. Handle JSON data of corresponding weight
4. Even under some conditions, we will use it to realize simple deep copy
5,......
JSON.stringify() will return different results when undefined, arbitrary function and symbol are used as the value of object attribute, array element and separate value respectively
1. When undefined, any function and symbol are used as object attribute values, JSON.stringify() will skip (ignore) serializing them
const data = { a: 'a', b: undefined, c: Symbol("c"), fn: function(){ return 'fn'; }, }; JSON.stringify(data); // {a: a}
2. When undefined, any function and symbol are used as array elements, JSON.stringify() will serialize them to null
JSON.stringify([ 'a', undefined, function fn(){ return "fn"; }, Symbol("c"), ]}; // "["a", null, null, null]"
3. Undefined will be returned when undefined, any function and symbol are serialized as separate values by JSON.stringify().
JSON.stringify(function a(){ console.log("a"); }); // undefined JSON.stringify(undefined); // undefined JSON.stringify(Symbol("c")); // undefined
Properties of non array objects cannot be guaranteed to appear in a serialized string in a specific order.
It should be noted that in 1.1, some special values will be ignored during JSON.stringify serialization. Therefore, there is no guarantee that the serialized strings will appear in a specific order (except arrays)
const data = { a: 'a', b: undefined, c: Symbol("c"), fn:function (){ return "fn"; }, d: "d", }; JSON.stringify(data); // "{"a": "a", "d":"d"}" JSON.stringify([ "a", undefined, function fn(){ return "fn" }, Symbol("c"), (d: "d"), ]); //"["a", null, null, null, "d"]"
When converting values, if there is a to.JSON() function, the value returned by this function is what the serialization result is, and the values of other properties are ignored.
JSON.stringify({ str: "str", toJSON: function () { return "strToJson"; }, }); // "strToJson"
JSON.stringify() will serialize the value of Date normally
JSON.stringify({now: new Date()}); // "{"now":"2021-02-01T05:00:54.082Z"}"
In fact, the Date object deploys its own toJSON() method, so the Date object will be treated as a string.
Values in NaN and infinity formats and null are treated as null
JSON.stringify(NaN); // "null" JSON.stringify(null); // "null" JSON.stringify(Infinity); // "null"
The wrapper objects of Boolean value, number and string will be automatically converted into the corresponding original value during serialization
JSON.stringify([new Number(1), new String("false"), new Boolean(false)]); // "[1,"false",false]"
Other types of objects, including Map/Set/WeakMap/WeakSet, serialize only enumerable properties
// Non enumerable properties are ignored by default: JSON.stringify( Object.create(null, { x: { value: "json", enumerable: false }, y: { value: "stringify", enumerable: true }, }) ); // "{"y":"stringify"}"
The simplest and crudest way to implement deep copy is serialization: JSON.parse(JSON.stringify(obj)). This way to implement deep copy will lead to many pits due to many characteristics of serialization, such as circular reference
// Executing this method on objects that contain circular references (objects refer to each other to form an infinite loop) will throw an error. const obj = { name: "loopObj", }; const loopObj = { obj, }; // Circular references are formed between objects to form a closed loop obj.loopObj = loopObj; // Encapsulates a deep copy of the function function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } // Execute a deep copy and throw an error deepClone(obj); /** VM44:9 Uncaught TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'loopObj' -> object with constructor 'Object' --- property 'obj' closes the circle at JSON.stringify (<anonymous>) at deepClone (<anonymous>:9:26) at <anonymous>:11:13 */
Executing this method on objects that contain circular references (objects refer to each other to form an infinite loop) will throw an error. This is why when serialization is used to implement deep copy, circular referenced objects will throw errors
All attributes with symbol as the attribute key will be completely ignored
JSON.stringify({ [Symbol.for("key")]: "json" }); // {}
When trying to convert a value of BigInt type, typeerror will be thrown (BigInt value cannot be JSON serialized)
const data = { num: BigInt(10), }; JSON.stringify(data); // Uncaught TypeError: Do not know how to serialize a BigInt
JSON.stringify second parameter replacer
The second argument can be a function or an array. As a function, it has two parameters, key and value. The function is similar to the callback function of array methods such as map and filter. The function will be executed once for each attribute value. If it is an array, the value of the array represents the property name that will be serialized into a JSON string
As a function
You can break most of the above features
const data = { a: "a", b: undefined, c: Symbol("c"), fn: function () { return "fn"; }, }; JSON.stringify(data, (key, value) => { switch (true) { case typeof value === "undefined": return "undefined"; case typeof value === "symbol": return value.toString(); case typeof value === "function": return value.toString(); default: break; } return value; }); // "{"a":"a","b":"undefined","c":"Symbol(c)","fn":"function () {\n return \"fn\";\n }"}"
However, it should be noted that when the second parameter is used as a function, the first parameter passed into the function is not the first key value pair of the object, but an empty string as the key value, and the value value is the key value pair of the whole object.
const data = { a: 1, b: 2, }; JSON.stringify(data, (key, value) => { console.log(key, "-", value); return value; }); // - {a: 1, b: 2} // a - 1 // b - 2
The second parameter is used as an array
The value of the array represents the property name that will be serialized into a JSON string
const data = { a: 1, b: 2, }; JSON.stringify(data, ["a"]); // "{"a":1}" JSON.stringify(data, ["b"]); // "{"b":2}" JSON.stringify(data, ["a", "b"]); // "{"a":1,"b":2}"
JSON.stringify third parameter space
Specify a blank string for indentation to beautify the output; If the parameter is a number, it represents how many spaces there are; The upper limit is 10. If the value is less than 1, it means that there are no spaces; If the parameter is a string (when the length of the string exceeds 10 letters, take the first 10 letters), the string will be used as a space; If the parameter is not provided (or null), there will be no spaces.
const data = { a: 1, b: 2, }; JSON.stringify(data, null, 2); /* "{ "a": 1, "b": 2 }" */
JSON.stringify and traversal comparison
const map1 = {}; const map2 = {}; for (let i = 0; i < 1000000; i++) { map1[i] = i; map2[i] = i; } function f1() { console.time("jsonString"); const start = new Date().getTime(); const r = JSON.stringify(map1) == JSON.stringify(map2); console.timeEnd("jsonString", r); } function f2() { console.time("map"); const r = Object.keys(map1).every((key) => { if (map2[key] || map2[key] === 0) { return true; } else { return false; } }); console.timeEnd("map", r); } f1(); f2(); // jsonString: 540.698974609375ms // map: 124.853759765625ms
You can see that the time difference between JSON.stringify and traversal comparison is nearly 5 times. In fact, the bottom layer of JSON api is traversal and converted into string, so the performance will be poor.