深入了解JSON.stringify

125 阅读5分钟

语法与返回值

  • 语法:JSON.stringify(value, replacer, space)

  • value:是必选字段。就是你输入的对象,比如数组,类等

  • replacer:可选。它又分为2种方式,一种是数组,第二种是方法。

    1、如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化

    2、如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理 

  • space:可选,指定缩进用的空白字符串,用于美化输出

    1)如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格

    2)如果仅仅是字符串,就在每行输出值的时候把这些字符串附加上去。当然,最大长度也是10个字符

    3)如果是一些转义字符,比如“\t”,表示回车,那么它每行一个回车

    4)如果该参数没有提供(或者为 null),将没有空格。

  • 返回值:一个表示给定值的JSON字符串。 异常[2]

  • 当在循环引用时会抛出异常TypeError ("cyclic object value")(循环对象值)

  • 当尝试去转换 BigInt 类型的值会抛出TypeError ("BigInt value can't be serialized in JSON")(BigInt值不能JSON序列化)

9大特性

特性一

  1. undefined任意的函数以及symbol值,出现在非数组对象的属性值中时在序列化过程中会被忽略
  2. undefined任意的函数以及symbol值出现在数组中时会被转换成 null
  3. undefined任意的函数以及symbol值单独转换时,会返回 undefined

// 1. 对象中存在这三种值会被忽略
console.log(JSON.stringify({
  name: '我只是一个名字',
  sex: 'boy',
  // 函数会被忽略
  showName () {
    console.log('我只是一个名字')
  },
  // undefined会被忽略
  age: undefined,
  // Symbol会被忽略
  symbolName: Symbol('我只是一个名字')
}))
// '{"name":"我只是一个名字","sex":"boy"}'

// 2. 数组中存在着三种值会被转化为null
console.log(JSON.stringify([
  '我只是一个名字',
  'boy',
  // 函数会被转化为null
  function showName () {
    console.log('我只是一个名字')
  },
  //undefined会被转化为null
  undefined,
  //Symbol会被转化为null
  Symbol('我只是一个名字')
]))
// '["我只是一个名字","boy",null,null,null]'

// 3.单独转换会返回undefined
console.log(JSON.stringify(
  function showName () {
    console.log('我只是一个名字')
  }
)) // undefined
console.log(JSON.stringify(undefined)) // undefined
console.log(JSON.stringify(Symbol('我只是一个名字'))) // undefined

特性二

布尔值数字字符串的包装对象在序列化过程中会自动转换成对应的原始值。

console.log(JSON.stringify([new Number(1), new String("字符串"), new Boolean(false)]))
// '[1,"字符串",false]'

特性三

所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。

console.log(JSON.stringify({
  name: Symbol('字符串'),
}))
// '{}'
console.log(JSON.stringify({
  [ Symbol('字符串') ]: '字符串',
}, (key, value) => {
  if (typeof key === 'symbol') {
    return value
  }
}))
// undefined

特性四

NaNInfinity 格式的数值及 null 都会被当做 null

console.log(JSON.stringify({
  age: NaN,
  age1: Infinity,
  name: null
}))
// '{"agz":null,"age1":null,"name":null}'

特性五

转换值如果有 toJSON()方法,该方法定义什么值将被序列化。

const toJSONObj = {
  name: '字符串',
  toJSON () {
    return 'JSON.stringify'
  }
}

console.log(JSON.stringify(toJSONObj))
// "JSON.stringify"

特性六

Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。

const d = new Date()

console.log(d.toJSON()) // 2021-10-05T14:01:23.932Z
console.log(JSON.stringify(d)) // "2021-10-05T14:01:23.932Z"

特性七

对包含循环引用的对象(对象之间相互引用,形成无限循环)执行此方法,会抛出错误。

let cyclicObj = {
  name: '字符串',
}

cyclicObj.obj = cyclicObj

console.log(JSON.stringify(cyclicObj))
// Converting circular structure to JSON

特性八

其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性

let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: '字符串',
    enumerable: true
  },
  sex: {
    value: 'boy',
    enumerable: false
  },
})

console.log(JSON.stringify(enumerableObj))
// '{"name":"字符串"}'

特性九

当尝试去转换 BigInt 类型的值会抛出错误

const alsoHuge = BigInt(9007199254740991)

console.log(JSON.stringify(alsoHuge))
// TypeError: Do not know how to serialize a BigInt

手写一个JSON.stringify

const jsonstringify = (data) => {
  // 确认一个对象是否存在循环引用
  const isCyclic = (obj) => {
  // 使用Set数据类型来存储已经检测过的对象
  let stackSet = new Set()
  let detected = false

  const detect = (obj) => {
    // 不是对象类型的话,可以直接跳过
    if (obj && typeof obj != 'object') {
      return
    }
    // 当要检查的对象已经存在于stackSet中时,表示存在循环引用
    if (stackSet.has(obj)) {
      return detected = true
    }
    // 将当前obj存如stackSet
    stackSet.add(obj)

    for (let key in obj) {
      // 对obj下的属性进行挨个检测
      if (obj.hasOwnProperty(key)) {
        detect(obj[key])
      }
    }
    // 平级检测完成之后,将当前对象删除,防止误判
    /*
      例如:对象的属性指向同一引用,如果不删除的话,会被认为是循环引用
      let tempObj = {
        name: '字符串'
      }
      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()
  }

  // 非对象
  if (type !== 'object' || data === null) {
    let result = data
    // 特性四:
    // NaN 和 Infinity 格式的数值及 null 都会被当做 null。
    if ([NaN, Infinity, null].includes(data)) {
      result = 'null'
      // 特性一:
      // `undefined`、`任意的函数`以及`symbol值`被`单独转换`时,会返回 undefined
    } else if (commonKeys1.includes(type)) {
      // 直接得到undefined,并不是一个字符串'undefined'
      return undefined
    } else if (type === 'string') {
      result = '"' + data + '"'
    }

    return String(result)
  } else if (type === 'object') {
    // 特性五:
    // 转换值如果有 toJSON() 方法,该方法定义什么值将被序列化
    // 特性六:
    // Date 日期调用了 toJSON() 将其转换为了 string 字符串(同Date.toISOString()),因此会被当做字符串处理。
    if (typeof data.toJSON === 'function') {
      return jsonstringify(data.toJSON())
    } else if (Array.isArray(data)) {
      let result = data.map((it) => {
        // 特性一:
        // `undefined`、`任意的函数`以及`symbol值`出现在`数组`中时会被转换成 `null`
        return commonKeys1.includes(typeof it) ? 'null' : jsonstringify(it)
      })

      return `[${result}]`.replace(/'/g, '"')
    } else {
      // 特性二:
      // 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
      if (['boolean', 'number'].includes(getType(data))) {
        return String(data)
      } else if (getType(data) === 'string') {
        return '"' + data + '"'
      } else {
        let result = []
        // 特性八
        // 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
        Object.keys(data).forEach((key) => {
          // 特性三:
          // 所有以symbol为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们。
          if (typeof key !== 'symbol') {
            const value = data[key]
            // 特性一
            // `undefined`、`任意的函数`以及`symbol值`,出现在`非数组对象`的属性值中时在序列化过程中会被忽略
            if (!commonKeys1.includes(typeof value)) {
              result.push(`"${key}":${jsonstringify(value)}`)
            }
          }
        })

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