Libon

JSON.stringify

2023/02/10 实现一个和 JSON.stringify 一样的方法 #JavaScript

ToC

function jsonStringify (data) {
  // 检查对象是否有循环引用
  const isCyclic = (obj) => {
    // 使用Set数据类型存储检测到的对象
    const stackSet = new Set()
    let detected = false

    const detect = (obj) => {
      // 如果它不是对象类型,可以直接跳过它
      if (obj && typeof obj !== 'object') {
        return
      }

      // 当要检查的对象在stackSet中已经存在时,这意味着存在循环引用
      if (stackSet.has(obj)) {
        return (detected = true)
      }
      // 将当前 obj 保存在Set
      stackSet.add(obj)

      for (const key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          detect(obj[key])
        }
      }
      // 级别检测完成后,请删除当前对象,以免误判
      /*
        对象的属性指向同一个引用, 如未删除,则视为循环引用
        let tempObj = {
          name: 'fatfish'
        }
        let obj4 = {
          obj1: tempObj,
          obj2: tempObj
        }
      */
      stackSet.delete(obj)
    }

    detect(obj)

    return detected
  }

  // 在包含循环引用的对象上执行此方法将引发错误。
  if (isCyclic(data)) {
    throw new TypeError('Converting circular structure to JSON')
  }

  // 在尝试转换BigInt类型的值时会抛出一个错误
  if (typeof data === 'bigint') {
    throw new TypeError('Do not know how to serialize a BigInt')
  }

  const type = typeof data
  const commonKeys1 = ['undefined', 'function', 'symbol']
  const getType = (s) => {
    return Object.prototype.toString.call(s).replace(/\[object (.*?)\]/, '$1').toLowerCase()
  }

  // not an object
  if (type !== 'object' || data === null) {
    let result = data

    // 数字Infinity和NaN,以及值null,都被认为是null。
    if ([NaN, Infinity, null].includes(data)) {
      result = 'null'
      // undefined、Function和Symbol不是有效的JSON值。
      // 如果在转换过程中遇到任何这样的值,它们要么被省略(当在对象中找到时),要么被更改为null(当在数组中找到时).
      // 当传入像JSON.stringify(function(){})或JSON.stringify(undefined)这样的“纯”值时,JSON.stringify()可以返回undefined。
    } else if (commonKeys1.includes(type)) {
      return undefined
    } else if (type === 'string') {
      result = '"' + data + '"'
    }

    return String(result)
  } else if (type === 'object') {
    // 如果值具有toJSON()方法,则它负责定义将序列化的数据。
    // Date的实例通过返回一个字符串(与Date . toisostring()相同)来实现toJSON()函数。
    // 因此,它们被视为字符串。
    if (typeof data.toJSON === 'function') {
      return jsonstringify(data.toJSON())
    } else if (Array.isArray(data)) {
      const result = data.map((it) => {
        // 如果在转换过程中遇到任何这样的值,它们要么被省略(当在对象中找到时),要么被更改为null(当在数组中找到时)
        return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
      })

      return `[${result}]`.replace(/'/g, '"')
    } else {
      // Boolean、Number和String对象在字符串化过程中按照传统的转换语义转换为相应的基元值。
      if (['boolean', 'number'].includes(getType(data))) {
        return String(data)
      } else if (getType(data) === 'string') {
        return '"' + data + '"'
      } else {
        const result = []

        // 所有其他Object实例(包括Map、Set、WeakMap和WeakSet)将只序列化它们的可枚举属性。
        Object.keys(data).forEach((key) => {
          // 所有以符号为键的属性将被完全忽略,即使在使用替换函数时也是如此。
          if (typeof key !== 'symbol') {
            const value = data[key]

            // undefined、Function和Symbol不是有效的JSON值。
            if (!commonKeys1.includes(typeof value)) {
              result.push(`"${key}":${jsonstringify(value)}`)
            }
          }
        })

        return `{${result}}`.replace(/'/, '"')
      }
    }
  }
}
CD ..