深入 JSON.stringfy

306 阅读6分钟

前言

  • JSON.stringfy 的序列化规则是什么?
  • JSON.stringfy 为什么用作深拷贝,会出问题?
  • 有什么妙用的地方?

介绍

💡: JSON.stringify()  方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。

语法

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

参数

  • value: 序列化的值。

  • replacer : 灵活性的序列化某些值

    1. 函数:在序列化的过程中会将每一个值都会经过此函数进行处理。

    2. 数组:只有包含数组中的属性值才会被序列化到最终字符串。

    3. null或空:会对对象的所有属性序列化。

  • space : 指定缩进用的空白字符串(美化输出)

    1. 数字: 代表有多少空格,上限10

    2. 字符串:取前10个字符作为空格

    3. null或空: 没有空格

replacer

函数

作为函数, replacer会接收两个参数,分别为keyvalue

注意❗️: 在开始时,replacer 函数会被传入一个空字符串作为key, value 为序列化的值,随后会依次传入

  const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  
  const replacer = (key, value) => {
    console.log(key, value)
    /**
     *       {name: 'Jack', age: 18, sex: 1}  第一次打印的值
     *  name Jack                             第二次...
     *  age  18                               第三次...
     *  sex  1                                第四次...
     */

    return value
  }
  
  JSON.stringify(user, replacer)

如果 replacer 为函数,可以有的返回值:

  • 返回Number,转换为相应的字符串作为属性值被添加到json字符串
  const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  let isFirst = true
  const replacer = (key, value) => {
    if (isFirst) {
      isFirst = false;
      return value
    }
    
    return 1
  }
  // {"name":1,"age":1,"sex":1}
  console.log(JSON.stringify(user, replacer));

  • 返回String,作为属性值被添加到json字符串
  const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  let isFirst = true
  const replacer = (key, value) => {
    if (isFirst) {
      isFirst = false;
      return value
    }
    
    return 'str'
  }
  // {"name":"str","age":"str","sex":"str"}
  console.log(JSON.stringify(user, replacer));
  • 返回Boolean,"true" 或者 "false" 作为属性值被添加到json字符串

  const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  let isFirst = true
  const replacer = (key, value) => {
    if (isFirst) {
      isFirst = false;
      return value
    }
    return true
  }
  // {"name":true,"age":true,"sex":true}
  console.log(JSON.stringify(user, replacer));
  
  • 返回其他对象,对象中的每一个值会递归依次被序列化, 如果对象为函数,则不会被序列化
 const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  const replacer = (key, value) => {
    if (key === 'sex') {
      return {
        a: 1,
        b: 2
      }
    }
    return value
  }
  // {"name":"Jack","age":18,"sex":{"a":1,"b":2}}
  console.log(JSON.stringify(user, replacer));

  • 返回 undefined,该属性值不会在 JSON 字符串中输出 注意❗️: 不能用 replacer 方法,从数组中移除值(values),如若返回 undefined 或者一个函数,将会被 null 取代
 const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  const replacer = (key, value) => {
    if (key === 'sex') {
      return undefined
    }

    if (key === 'age') {
      return () => {}
    }
    
    return value
  }
  // {"name":"Jack"}
  console.log(JSON.stringify(user, replacer));

尝试改变一下数组, 可以看到 如果返回值为 undefined 或者说函数,在数组中会被转换为 null

  const userNames = ['zhenzhen', 'lianlian', 'aiai'];
  const replacer = (key, value) => {

    if (value === 'zhenzhen') {
      return undefined
    }

    if (value === 'lianlian') {
      return () => {}
    }

    return value
  }
  
  // [null,null,"aiai"]
  console.log(JSON.stringify(userNames, replacer));
数组

可以理解为可以提取对象中的 key

  const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }
  const replacer = ['name', 'age']
  console.log(JSON.stringify(user, replacer));  // {"name":"Jack","age":18}

space

数字

为数字的话会将序列化的字符串每个键值对前加上n个空格


 const user = {
    name: 'Jack',
    age: 18,
    sex: 1
  }

  console.log(JSON.stringify(user, null));
  /**
   * {"name":"Jack","age":18,"sex":1}
   */

  console.log(JSON.stringify(user, null, 4));
  /*
  {
      "name": "Jack",     
      "age": 18,
      "sex": 1
  }
  */
字符串

会将字符串添加到每个键值对之前,可以看到支持转义字符


 const user = {
  name: 'Jack',
  age: 18,
  sex: 1
}

console.log(JSON.stringify(user, null, '\n'));
/*
{

"name": "Jack",

"age": 18,

"sex": 1
}
*/

console.log(JSON.stringify(user, null, '\t'));
/*
{
      "name": "Jack",
      "age": 18,
      "sex": 1
}
*/
console.log(JSON.stringify(user, null, 'test'));
/*
{
test"name": "Jack",
test"age": 18,
test"sex": 1
}
*/

规则

JSON.stringfy 的序列化规则是什么?

  • 如果被转换的对象有 toJSON 方法,则该方法返回什么值,将会序列化什么值
 const user = {
    name: 'Jack',
    age: 18,
    sex: 1,
    toJSON() {
      return {
        a: 1,
        b: 2
      }
    }
  }


  // {"a":1,"b":2}
  console.log(JSON.stringify(user));
  
  • 非数组对象的属性不能保证以特定的顺序出现在序列化后的字符串中

在对象中, 键值是无序的存在,所以被序列化之后不能保证有序的存在, 如果需要有序,可以使用 Map

image.png

const user = {
  2: 18,
  0: 'Jack',
  1: 1,
}


// {"0":"Jack","1":1,"2":18}
console.log(JSON.stringify(user));
  • 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。

JSON.stringify({});                        // '{}'
JSON.stringify(true);                      // 'true'
JSON.stringify("foo");                     // '"foo"'
JSON.stringify([1, "false", false]);       // '[1,"false",false]'

  • undefined 、函数、 symbol 值,在序列化的过程中会被忽略, 单独转换时,会被转换为 undefined (在数组中会为转为 null

const test = {
  key1: () => {},
  key2: Symbol(123),
  key3: undefined,
  key4: 123,
  [Symbol(456)]: 456,
}
// {"key4":123}
console.log(JSON.stringify(test));

const testArr = [() => {}, undefined, Symbol(123), 123];

// [null,null,null,123]
console.log(JSON.stringify(testArr))

  • 转换循环引用的对象,会报出错误

image.png

  const test = {
    key1: 'key1',
  }
  const loopTest = test;
  test.loopTest = loopTest
  console.log(JSON.stringify(loopTest))  // TypeError 
  
  • 所有以 symbol 为属性键的属性都会被完全忽略掉,即便 replacer 参数中强制指定包含了它们

const test = {
  [Symbol(123)]: 123,
}
const replacer = (key, value) => {
  if(typeof key === "symbol") {
    return 'symbool'
  }
  return value
}

// {}
console.log(JSON.stringify(test, replacer))

  • 序列化 Date 日期对象, 会调用 toJSON 方法将其转换为字符串

image.png


const date = new Date()

// "2022-05-12T08:40:30.446Z"
console.log(JSON.stringify(date))

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

 const test = {
  key1: NaN,
  key2: Infinity,
  key3: null,
  key4: 'key4'
}

// {"key1":null,"key2":null,"key3":null,"key4":"key4"}
console.log(JSON.stringify(test));
  • 其他类型的对象,包括 Map/Set/WeakMap/WeakSet,仅会序列化可枚举的属性
 const obj = {}
 Object.defineProperties(obj, {
  key1: {
    value: 'key1',
    enumerable: false
  },
  key2: {
    value: 'key2',
    enumerable: true
  }
})

// {"key2":"key2"}
console.log(JSON.stringify(obj));

总结

  • 拷贝的对象有重写 toJSON 方法,有可能会出问题
  • 转换循环引用对象,会报错
  • 函数、undefinedSymbool 会忽略
  • NaNInfinity 会被转换为 null
  • Symbol 为 键的 会被忽略
  • Date 日期对象 会调用 toJSON 方法
  • 不可枚举属性不会被转换

为什么不能用作深拷贝也迎刃而解了

妙用

注意⚠️ :前提是 避免上述规则数据丢失的情况下

localStorage

通常情况下,我们存储复杂数据类型到本地存储,可以将数据序列化为json格式的字符串进行存储

const user = {
  name: 'Jack',
  age: 18,
}
// bad
localStorage.setItem('test1', user)
const storageUser1 = localStorage.getItem('test1')

// [object Object]
console.log(storageUser1)


// good
localStorage.setItem('test2', JSON.stringify(user))
const storageUser2 = JSON.parse(localStorage.getItem('test2'))

// {name: 'Jack', age: 18}
console.log(storageUser2)

过滤对象的属性

  const user = {
  name: 'Jack',
  age: 18,
  sex: 1
}

// {name: 'Jack', age: 18}
console.log(JSON.parse(JSON.stringify(user, ['name', 'age'])))

深拷贝


  const user = {
    name: 'Jack',
    hobby: ['唱', '跳']
  }

  const cloneUser = JSON.parse(JSON.stringify(user))
  cloneUser.hobby.pop()

  // {name: 'Jack', hobby: ['唱', '跳']}
  console.log(user);

  // {name: 'Jack', hobby: ['唱']}
  console.log(cloneUser);
  

对象的 map 函数

const obj = {
  key1: 1,
  key2: 2,
  key3: 3,
}
const replacer = (key, value) => {
  if (typeof value === 'number') {
    return value * 2
  }

  return value
};

const mapObj = JSON.parse(JSON.stringify(obj, replacer))

// {key1: 2, key2: 4, key3: 6}
console.log(mapObj)

删除对象属性


 const obj = {
    key1: 1,
    key2: 2,
    key3: 3,
  }
  const replacer = (key, value) => {
    if (key === 'key1') {
      return undefined
    }

    return value
  };

  const removeObj = JSON.parse(JSON.stringify(obj, replacer))

  // {key2: 2, key3: 3}
  console.log(removeObj)
  

判断对象中是否包含某个值

//判断数组是否包含某对象
const names = [
  { name: 'Jack' },
];
const jack = { name: 'Jack' };

// true
console.log(JSON.stringify(names).includes(JSON.stringify(jack)))

判断对象是否相等

// 判断对象是否相等
const d1 = { type: 'div' }
const d2 = { type: 'div' }

// true
console.log(JSON.stringify(d1) === JSON.stringify(d2))

对象数组去重


function unique(arr) {
  const uniqueArr = arr.reduce(
    (pre, val, index, thisArr) => {
      pre[JSON.stringify(val)] = val;
      return thisArr.length - 1 === index ? Object.values(pre) : pre
    },
    {}
  );
  return uniqueArr
}

console.log(
  unique([{}, {},
  { x: 1 },
  { x: 1 },
  { a: 1 },
  { x: 1, a: 1 },
  { x: 1, a: 1 },
  { x: 1, a: 1, b: 1 }
  ])
);

参考 juejin.cn/post/707212…