首页
开发技巧正文内容

深入了解 JSON.stringify()

2024年09月09日
阅读时长 2 分钟
阅读量 3
深入了解 JSON.stringify()

概念

我们熟悉 JSON.stringify,通常用于序列化(深拷贝)。它将我们的对象转换为 JSON 字符串。这种方法在我们的工作中确实非常方便,但是这种方法也有一些缺点,我们很少遇到。

缺点

1. 对函数不友好

如果对象的属性是一个函数,这个属性在序列化过程中将会丢失。

let obj = {
    name: 'iyongbao',
    foo: function () {
        console.log(`${ this.name }`)
    }
}

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

2. 对 undefined 不友好

如果对象的属性值是 undefined,转换后将会丢失。

let obj = {
    name: undefined
}

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

3. 对正则表达式不友好

如果对象的属性是一个正则表达式,在转换后将变成一个空对象。

let obj = {
    name: 'iyongbao',
    zoo: /^i/ig,
    foo: function () {
        console.log(`${ this.name }`)
    }
}

console.log(JSON.stringify(obj)); // {"name":"iyongbao","zoo":{}}

4. 数组对象

如果是数组对象,上述情况同样会发生。

let arr = [
    {
        name: undefined
    }
]

console.log(JSON.stringify(arr)); // [{}]

特点

第一点

  • undefined、任何 functionsymbol 这三个特殊值被用作对象属性、数组元素或独立值时,JSON.stringify() 将返回不同的结果。
const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  }
};
JSON.stringify(data); // "{"a":"aaa"}"
  • undefined、任何 functionsymbol 被用作数组元素值时,JSON.stringify() 将把它们序列化为 null
JSON.stringify(["aaa", undefined, function aa() {
    return true
  }, Symbol('dd')])  // "["aaa",null,null,null]"
  • undefined、任何 functionsymbol 被独立值序列化时,将返回 undefined
JSON.stringify(function a (){console.log('a')})
// undefined
JSON.stringify(undefined)
// undefined
JSON.stringify(Symbol('dd'))
// undefined

第二点

非数组对象的属性在序列化字符串中的出现顺序无法保证。正如第一点所述,JSON.stringify() 在序列化过程中忽略了一些特殊值,因此无法保证序列化后的字符串仍然以特定顺序出现(除了数组)。

const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  },
  d: "ddd"
};
JSON.stringify(data); // "{"a":"aaa","d":"ddd"}"

JSON.stringify(["aaa", undefined, function aa() {
    return true
  }, Symbol('dd'),"eee"])  // "["aaa",null,null,null,"eee"]"

第三点

如果要转换的值具有 toJSON() 函数,则序列化结果将是该函数返回的任何值,其他属性的值将被忽略。

JSON.stringify({
    say: "hello JSON.stringify",
    toJSON: function() {
      return "today i learn";
    }
  })
// "today i learn"

第四点

JSON.stringify() 将正常序列化 Date 值。实际上,Date 对象本身实现了 toJSON() 方法(相当于 Date.toISOString()),因此 Date 对象被视为字符串。

JSON.stringify({ now: new Date() });
// "{"now":"2024-06-16T12:43:13.577Z"}"

第五点

NaNInfinity 格式的数值,以及 null,都将被视为 null

JSON.stringify(NaN)
// "null"
JSON.stringify(null)
// "null"
JSON.stringify(Infinity)
// "null"

第六点

布尔值、数字和字符串的包装对象在序列化过程中将自动转换为相应的原始值。

JSON.stringify([new Number(1), new String("false"), new Boolean(false)]);
// "[1,"false",false]"

第七点

其他类型的对象,包括 Map/Set/WeakMap/WeakSet,只会序列化可枚举属性。默认情况下,不可枚举属性将被忽略。

JSON.stringify( 
    Object.create(
        null, 
        { 
            x: { value: 'json', enumerable: false }, 
            y: { value: 'stringify', enumerable: true } 
        }
    )
);
// "{"y":"stringify"}"

第八点

我们都知道实现深度克隆的最简单和最原始的方法是序列化:JSON.parse(JSON.stringify())。由于序列化的各种特性,这种实现深度克隆的方法会导致许多问题,例如我们现在正在解决的循环引用问题。

// 在具有循环引用的对象(相互引用形成无限循环的对象)上执行此方法将会抛出错误。
const obj = {
  name: "loopObj"
};
const loopObj = {
  obj
};
// 对象形成循环引用,创建一个闭环
obj.loopObj = loopObj;

// 封装一个深度克隆函数
function deepClone(obj) {
  return JSON.parse(JSON.stringify(obj));
}
// 执行深度克隆,抛出错误
deepClone(obj)
/**
 VM44:9 Uncaught TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'loopObj' -> object with constructor 'Object'
    --- property 'obj' closes the circle
    at JSON.stringify (<anonymous>)
    at deepClone (<anonymous>:9:26)
    at <anonymous>:11:13
 */

第九点

symbol 作为属性键的属性将被完全忽略,即使它们明确包含在替换器参数中。

JSON.stringify({ [Symbol.for("json")]: "stringify" }, function(k, v) {
    if (typeof k === "symbol") {
      return v;
    }
  })

// undefined

拓展

JSON.stringify 还有可选的第二和第三参数。

1. 第二参数 replacer

第二个参数 replacer 可以采用两种形式:函数或数组。作为函数时,它接收两个参数,键和值。该函数类似于数组方法(如 map、filter 等)中的回调函数,对每个属性值执行一次。如果 replacer 是一个数组,则数组中的值表示将被序列化为 JSON 字符串的属性的名称。

作为函数使用

const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  }
};
// 不使用替换器参数
JSON.stringify(data); 
// "{"a":"aaa"}"

// 使用替换器参数作为函数时
JSON.stringify(data, (key, value) => {
  switch (true) {
    case typeof value === "undefined":
      return "undefined";
    case typeof value === "symbol":
      return value.toString();
    case typeof value === "function":
      return value.toString();
    default:
      break;
  }
  return value;
})
// "{"a":"aaa","b":"undefined","c":"Symbol(dd)","fn":"function() {\n    return true;\n  }"}"

当使用替换器函数时,传递给该函数的第一个参数不是对象的第一个键值对。相反,一个空字符串被用作键,值是整个对象的键值对:

const data = {
  a: 2,
  b: 3,
  c: 4,
  d: 5
};
JSON.stringify(data, (key, value) => {
  console.log(value);
  return value;
})
// 传递给替换器函数的第一个参数是 {"":{a: 2, b: 3, c: 4, d: 5}}
// {a: 2, b: 3, c: 4, d: 5}   
// 2
// 3
// 4
// 5

实现一个 map 函数

我们还可以使用它手动实现一个类似对象的 map 函数。

// 实现一个 map 函数
const data = {
  a: 2,
  b: 3,
  c: 4,
  d: 5
};
const objMap = (obj, fn) => {
  if (typeof fn !== "function") {
    throw new TypeError(`${fn} is not a function !`);
  }
  return JSON.parse(JSON.stringify(obj, fn));
};
objMap(data, (key, value) => {
  if (value % 2 === 0) {
    return value / 2;
  }
  return value;
});
// {a: 1, b: 3, c: 2, d: 5}

作为数组使用

当替换器作为数组使用时,结果非常直接。数组中的值表示将被序列化为 JSON 字符串的属性的名称。

const jsonObj = {
  name: "JSON.stringify",
  params: "obj,replacer,space"
};

// 仅保留 params 属性的值
JSON.stringify(jsonObj, ["params"]);
// "{"params":"obj,replacer,space"}"

2. 第三参数 space

第三个参数 space 用于控制结果字符串中的间距。让我们看一个例子来理解它的作用:

const tiedan = {
  name: "Jhon",
  describe: "JSON.stringify()",
  emotion: "like"
};
JSON.stringify(tiedan, null, "--");
// 输出如下
// "{
// --"name": "Jhon",
// --"describe": "JSON.stringify()",
// --"emotion": "like"
// }"
JSON.stringify(tiedan, null, 2);
// "{
//   "name": "Jhon",
//   "describe": "JSON.stringify()",
//   "emotion": "like"
// }"
免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。