Tell me more about some features of JSON.stringify? Which performance is better than traversal?

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.

 

Added by Kaz on Wed, 08 Dec 2021 02:46:58 +0200