首頁 > 軟體

學習JSON.stringify的9大特性和轉換規則

2022-02-14 13:00:38

一、JSON.stringify介紹

JSON.stringify()  方法將一個 JavaScript 物件或值轉換為 JSON 字串,如果指定了一個 replacer 函數,則可以選擇性地替換值,或者指定的 replacer 是陣列,則可選擇性地僅包含陣列指定的屬性。

語法:

JSON.stringify(value[, replacer [, space]])

引數:

value 將要序列化成 一個 JSON 字串的值。

replacer 可選

  • 如果該引數是一個函數,則在序列化過程中,被序列化的值的每個屬性都會經過該函數的轉換和處理;
  • 如果該引數是一個陣列,則只有包含在這個陣列中的屬性名才會被序列化到最終的 JSON 字串中;
  • 如果該引數為 null 或者未提供,則物件所有的屬性都會被序列化。

space 可選

  • 指定縮排用的空白字串,用於美化輸出(pretty-print);
  • 如果引數是個數位,它代表有多少的空格;上限為10。
  • 該值若小於1,則意味著沒有空格;
  • 如果該引數為字串(當字串長度超過10個字母,取其前10個字母),該字串將被作為空格;
  • 如果該引數沒有提供(或者為 null),將沒有空格。

返回值:

一個表示給定值的JSON字串。

異常:

  • 當在迴圈參照時會丟擲異常TypeError ("cyclic object value")(迴圈物件值)
  • 當嘗試去轉換 BigInt 型別的值會丟擲TypeError ("BigInt value can't be serialized in JSON")(BigInt值不能JSON序列化).

基本使用:

注意:

  • JSON.stringify可以轉換物件或者值(平常用的更多的是轉換物件)
  • 可以指定replacer為函數選擇性的地替換
  • 也可以指定replacer為陣列,可轉換指定的屬性

這裡僅僅是MDN上關於JSON.stringify其中最基礎的說明,咱們先打個碼試試這幾個特性

1. 轉換物件

console.log(JSON.stringify({ name: '前端胖頭魚', sex: 'boy' })) // '{"name":"前端胖頭魚","sex":"boy"}'

 2. 轉換普通值

console.log(JSON.stringify('前端胖頭魚')) // "前端胖頭魚"
console.log(JSON.stringify(1)) // "1"
console.log(JSON.stringify(true)) // "true"
console.log(JSON.stringify(null)) // "null"

3. 指定replacer函數

console.log(JSON.stringify({ name: '前端胖頭魚', sex: 'boy', age: 100 }, (key, value) => {
  return typeof value === 'number' ? undefined : value
}))
// '{"name":"前端胖頭魚","sex":"boy"}'

4. 指定陣列

console.log(JSON.stringify({ name: '前端胖頭魚', sex: 'boy', age: 100 }, [ 'name' ]))
// '{"name":"前端胖頭魚"}'

 5. 指定space(美化輸出)

console.log(JSON.stringify({ name: '前端胖頭魚', sex: 'boy', age: 100 }))
// '{"name":"前端胖頭魚","sex":"boy","age":100}'
console.log(JSON.stringify({ name: '前端胖頭魚', sex: 'boy', age: 100 }, null , 2))
/*
{
  "name": "前端胖頭魚",
  "sex": "boy",
  "age": 100
}
*/

二、9大特性要記住

以前僅僅是使用了這個方法,卻沒有詳細瞭解他的轉換規則,居然有9個之多。

特性一

undefined、任意的函數以及symbol值,出現在非陣列物件的屬性值中時在序列化過程中會被忽略
undefined、任意的函數以及symbol值出現在陣列中時會被轉換成 null。
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({
  [Symbol('前端胖頭魚')]: '前端胖頭魚'}
)) 
// '{}'
console.log(JSON.stringify({
  [ Symbol('前端胖頭魚') ]: '前端胖頭魚',
}, (key, value) => {
  if (typeof key === 'symbol') {
    return value
  }
}))
// undefined

特性四

NaNInfinity 格式的數值及 null 都會被當做 null。

console.log(JSON.stringify({
  age: NaN,
  age2: Infinity,
  name: null
}))
// '{"age":null,"age2":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

終於重新學完JSON.stringify的眾多特性啦!咱們根據這些特性來手寫一個簡單版本的吧(無replacer函數和space)

原始碼實現:

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(/'/, '"')
      }
    }
  }
}

測試一把:

// 1. 測試一下基本輸出
console.log(jsonstringify(undefined)) // undefined 
console.log(jsonstringify(() => { })) // undefined
console.log(jsonstringify(Symbol('前端胖頭魚'))) // undefined
console.log(jsonstringify((NaN))) // null
console.log(jsonstringify((Infinity))) // null
console.log(jsonstringify((null))) // null
console.log(jsonstringify({
  name: '前端胖頭魚',
  toJSON() {
    return {
      name: '前端胖頭魚2',
      sex: 'boy'
    }
  }
}))
// {"name":"前端胖頭魚2","sex":"boy"}

// 2. 和原生的JSON.stringify轉換進行比較
console.log(jsonstringify(null) === JSON.stringify(null));
// true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
// true
console.log(jsonstringify(false) === JSON.stringify(false));
// true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
// true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
// true
let str = "前端胖頭魚";
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();
console.log(jsonstringify(date) === JSON.stringify(date));
// true
let sym = Symbol('前端胖頭魚');
console.log(jsonstringify(sym) === JSON.stringify(sym));
// true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
// true
let obj = {
  name: '前端胖頭魚',
  age: 18,
  attr: ['coding', 123],
  date: new Date(),
  uni: Symbol(2),
  sayHi: function () {
    console.log("hello world")
  },
  info: {
    age: 16,
    intro: {
      money: undefined,
      job: null
    }
  },
  pakingObj: {
    boolean: new Boolean(false),
    string: new String('前端胖頭魚'),
    number: new Number(1),
  }
}
console.log(jsonstringify(obj) === JSON.stringify(obj)) 
// true
console.log((jsonstringify(obj)))
// {"name":"前端胖頭魚","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖頭魚","number":1}}
console.log(JSON.stringify(obj))
// {"name":"前端胖頭魚","age":18,"attr":["coding",123],"date":"2021-10-06T14:59:58.306Z","info":{"age":16,"intro":{"job":null}},"pakingObj":{"boolean":false,"string":"前端胖頭魚","number":1}}

// 3. 測試可遍歷物件
let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: '前端胖頭魚',
    enumerable: true
  },
  sex: {
    value: 'boy',
    enumerable: false
  },
})

console.log(jsonstringify(enumerableObj))
// {"name":"前端胖頭魚"}

// 4. 測試迴圈參照和Bigint

let obj1 = { a: 'aa' }
let obj2 = { name: '前端胖頭魚', a: obj1, b: obj1 }
obj2.obj = obj2

console.log(jsonstringify(obj2))
// TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
// TypeError: Do not know how to serialize a BigInt

通過上面測試可以看出,jsonstringify基本和JSON.stringify表現一致,

到此這篇關於學習JSON.stringify的9大特性和轉換規則的文章就介紹到這了,更多相關JSON.stringify內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com