Json.stringify emulates implementation

Json.stringify is one of the most commonly used apis in our development, which converts a JavaScript object or value to a JSON string.

Look at its method signature: json.stringify (value[, replacer [, space]])

The first argument is the JavaScript object to be converted to a JSON string, the second argument is the function to replace the specified value, and the third argument is generally used for formatting.

This time we implement the case with only the first parameter.

Take out the description of MDN:

  • Abnormal situation

    • An exception TypeError (” Cyclic Object Value “) is thrown when a cyclic reference is made
    • When attempting to convert a value of type BigInt, TypeError is raised (“BigInt value can’t be serialized in JSON”).
  • The conversion processing

    • Convert values if there is a toJSON() method, which defines what values will be serialized.
    • Properties of non-array objects are not guaranteed to appear in a serialized string in a particular order.
    • Booleans, numbers, and string wrapper objects are automatically converted to their original values during serialization.
    • Undefined, arbitrary functions, and symbol values are ignored during serialization (when appearing in an attribute value of a non-array object) or converted to NULL (when appearing in an array). Function/undefined returns undefined when converted separately, such as json.stringify (function(){}) or json.stringify (undefined).
    • Executing this method on objects that contain circular references (objects that refer to each other in an infinite loop) throws an error.
    • All properties with symbol as the property key are completely ignored, even if they are mandatory in the replacer parameter.
    • Date the Date is converted to a string by calling toJSON() (same as date.toisostring ()), so it is treated as a string.
    • Values and nulls in NaN and Infinity formats are treated as null.

Analysis of the

If the object contains a circular reference, how to determine whether the object contains a circular reference? The general practice is to put all objects in a WeakMap, and judge whether the object has been stored in a WeakMap when traversing the object properties each time. If it has been stored, it can be concluded that the object contains circular references, as shown in the following code. (No justification, for example only)

const a = {}
a.a = a

const isValidObject = obj= > typeof obj === "object"&& obj ! = =null
function cycle(obj, map = new WeakMap(a)) {
  if (isValidObject(obj) && map.has(obj)) {
    throw Error("cyclic object value")
  }
  isValidObject(obj) && map.set(obj, true)
  for (let key in obj) {
    cycle(obj[key], map)
  }
}

cycle(a)
Copy the code

Why does WeakMap exist in this place? We know that when JS objects are recycled, they will use the marking algorithm to recycle the cyclic reference objects, while the reference relation in WeakMap is weak reference. When GC performs the marking operation, it will not be considered as an active object because it is referenced by WeakMap. So if it is not referenced by another object, the memory will be freed on the next GC execution to avoid wasting memory.

When attempting to convert a value of type BigInt, TypeError is raised (“BigInt value can’t be serialized in JSON”).

So how to judge an Object is BigInt, can use type substitution function Object. The prototype. ToString. Call (obj) to judge whether the value of [Object BigInt]

3. Judgment of NaN and Infinity

We can use Number. IsNaN to determine NaN, and Infinity to determine equality.

If an object has a toJSON property of type function, then the return value of the property is returned.

code

Nothing else is too hard to understand, the code and tests are as follows:

const isObject = obj= > typeof obj === "object"&& obj ! = =null

function jsonStringify(obj, map = new WeakMap(a)) {
  // Check for circular references first
  if (isObject(obj) && map.has(obj)) throw TypeError("cyclic object value")
  isObject(obj) && map.set(obj, true)
  / / determine BigInt
  if (Object.prototype.toString.call(obj) === "[object BigInt]") {
    throw TypeError("Do not know how to serialize a BigInt")}if (typeof obj === "string") {
    return `"${obj}"`
  }
  if (obj === Infinity || Number.isNaN(obj) || obj === null) {
    return "null"
  }
  if (
    obj === undefined ||
    typeof obj === "function" ||
    typeof obj === "symbol"
  ) {
    return undefined
  }
  if (isObject(obj)) {
    if (obj.toJSON && typeof obj.toJSON === "function") {
      return jsonStringify(obj.toJSON(), map)
    } else if (Array.isArray(obj)) {
      let result = []
      obj.forEach((item, index) = > {
        let value = jsonStringify(item, map)
        if(value ! = =undefined) {
          result.push(value)
        }
      })
      return ` [${result}] `
    } else {
      let result = []
      Object.keys(obj).forEach(item= > {
        let value = jsonStringify(obj[item], map)
        if(value ! = =undefined) {
          result.push(`"${item}":${value}`)}})return ("{" + result + "}").replace(/'/g.'"')}}return String(obj)
}

let nl = null
console.log(jsonStringify(nl) === JSON.stringify(nl))
// true

let und = undefined
console.log(jsonStringify(undefined) = = =JSON.stringify(undefined))
// true

let boo = false
console.log(jsonStringify(boo) === JSON.stringify(boo))
// true

let nan = NaN
console.log(jsonStringify(nan) === JSON.stringify(nan))
// true

let inf = Infinity
console.log(jsonStringify(Infinity) = = =JSON.stringify(Infinity))
// true

let str = "jack"
console.log(jsonStringify(str) === JSON.stringify(str))
// true

let reg = new RegExp("w")
console.log(jsonStringify(reg) === JSON.stringify(reg))
// true

let date = new Date(a)console.log(jsonStringify(date) === JSON.stringify(date))
// true

let sym = Symbol(1)
console.log(jsonStringify(sym) === JSON.stringify(sym))
// true

let array = [1.2.3]
console.log(jsonStringify(array) === JSON.stringify(array))
// true

let obj = {
  name: "jack".age: 18.attr: ["coding".123].date: new Date(0),
  uni: Symbol(2),
  sayHi: function () {
    console.log("hi")},info: {
    sister: "lily".age: 16.intro: {
      money: undefined.job: null,,}}}console.log(jsonStringify(obj) === JSON.stringify(obj))
// true
Copy the code