你不知道的 JSON.stringify!!!

开发 前端
JSON.stringify是我们经常用到的的一个方法,它主要作用是将 JavaScript 值和对象转换为字符串。

[[441493]]

JSON.stringify是我们经常用到的的一个方法,它主要作用是将 JavaScript 值和对象转换为字符串。如:

  1. JSON.stringify({ foo: "bar" }); 
  2. // => '{"foo":"bar"}' 
  3.  
  4. JSON.stringify(123); 
  5. // => '123' 

但是JS 的许多地方都有问题,这个函数也不例外。我们可能会想象一个叫做 "stringify "的函数总是返回一个字符串......但它并没有!

例如,如果你尝试 stringify undefined,它返回 undefined ,而不是一个字符串。

  1. JSON.stringify(undefined); 
  2. // => undefined 

接下来,我将分两部分讲:

  • 列举 JSON.stringify 不返回字符串的情况
  • 我们将如何避免这些陷阱

什么时候 JSON.stringify 不返回字符串?

undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined。

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

我认为 JSON.stringify 能够返回字符串以外的东西是挺惊讶的。但在6种情况下,它可以返回undefined:

试图在顶层对 undefined 进行序列化,会返回 undefined。

  1. JSON.stringify(undefined); 
  2. // => undefined 

尝试序列化函数也会返回 undefined。对于常规函数、箭头函数、异步函数和生成器函数都是如此。

  1. JSON.stringify(function foo() {}); 
  2. // => undefined 
  3.  
  4. JSON.stringify(() => {}); 
  5. // => undefined 
  6.  
  7. function bar() {} 
  8. bar.someProperty = 123; 
  9. JSON.stringify(bar); 
  10. // => undefined 

尝试序列化symbol 也会返回 undefined。

  1. JSON.stringify(Symbol("computers were a mistake")); 
  2. // => undefined 

在浏览器中,试图序列化被废弃的document.all 也会返回 undefined。

  1. // => undefined 

这只影响到浏览器,因为document.all在其他环境中是不可用的,比如Node。

带有 toJSON 函数的对象将被运行,而不是试图正常地序列化它们。但是如果 toJSON 返回上面的一个值,试图在顶层序列化它将导致 JSON.stringify 返回undefined。

  1. JSON.stringify({ toJSON: () => undefined }); 
  2. // => undefined 
  3.  
  4. JSON.stringify({ ignored: true, toJSON: () => undefined }); 
  5. // => undefined 
  6.  
  7. JSON.stringify({ toJSON: () => Symbol("heya") }); 
  8. // => undefined 

你可以传递第二个参数,称为 "replacer",它可以改变序列化的逻辑。如果这个函数为顶层返回上述值之一,JSON.stringify 将返回undefined。

  1. JSON.stringify({ ignored: true }, () => undefined); 
  2. // => undefined 
  3.  
  4. JSON.stringify(["ignored"], () => Symbol("hello")); 
  5. // => undefined 

需要注意的是,其中的许多东西实际上只影响到顶层的序列化。例如,JSON.stringify({foo: undefined}),返回字符串"{}",这并不令人惊讶。

我还想提一下,TypeScript的类型定义在这里是不正确的。例如,下面的代码类型的校验可以通过:

  1. const result: string = JSON.stringify(undefined); 

在第2部分中,我们将讨论如何更新 TypeScript 的定义以确保其正确性。

JSON.stringify 也可能遇到问题,导致它抛出一个错误。在正常情况下,有四种情况会发生:

循环引用会导致抛出一个类型错误。

  1. const b = { a }; 
  2. a.b = b; 
  3.  
  4. JSON.stringify(a); 
  5. // => TypeError: cyclic object value 

注意,这些错误消息在不同浏览器可能提示是不样的,例如,Firefox 的错误信息与Chrome的不同。

BigInts不能用JSON.stringify 进行序列化,这些也会导致一个TypeError。

  1. JSON.stringify(12345678987654321n); 
  2. // => TypeError: BigInt value can't be serialized in JSON 
  3.  
  4. JSON.stringify({ foo: 456n }); 
  5. // => TypeError: BigInt value can't be serialized in JSON 

带有 toJSON 函数的对象将被运行。如果这些函数抛出错误,它将冒泡到调用者。

  1. const obj = { 
  2.   foo: "ignored"
  3.   toJSON() { 
  4.     throw new Error("Oh no!"); 
  5.   }, 
  6. }; 
  7.  
  8. JSON.stringify(obj); 
  9. // => Error: Oh no

你可以传递第二个参数,称为 replacer。如果这个函数抛出一个错误,它将冒泡。

  1. JSON.stringify({}, () => { 
  2.   throw new Error("Uh oh!"); 
  3. }); 
  4. // => Error: Uh oh! 

现在我们已经看到了 JSON.stringify 不返回字符串的情况,接下来,我们来看看如何避免这些问题。

如何避免这些问题

没有关于如何解决这些缺陷的通用方法,所以这里只介绍一些常见的情况。

处理循环引用

根据个人经验,JSON.stringify 在传递循环引用时最容易出错。如果这对你来说是一个常见的问题,我推荐 json-stringify-safe 包,它能很好地处理这种情况。

  1. const stringifySafe = require("json-stringify-safe"); 
  2.  
  3. const a = {}; 
  4. const b = { a }; 
  5. a.b = b; 
  6.  
  7. JSON.stringify(a); 
  8. // => TypeError: cyclic object value 
  9.  
  10. stringifySafe(a); 
  11. // => '{"b":{"a":"[Circular ~]"}}' 

封装

你可能想用你自己的自定义函数来封装 JSON.stringify。你可以决定你想要它做什么。错误应该冒出来吗?如果 JSON.stringify 返回 undefined,应该怎么做?

例如,Signal Desktop有一个名为 reallyJsonStringify 的函数,它总是返回一个用于调试的字符串。就像这样

  1. function reallyJsonStringify(value) { 
  2.   let result; 
  3.   try { 
  4.     result = JSON.stringify(value); 
  5.   } catch (_err) { 
  6.     // If there's any error, treat it like `undefined`. 
  7.     result = undefined; 
  8.   } 
  9.  
  10.   if (typeof result === "string") { 
  11.     // It's a string, so we're good. 
  12.     return result; 
  13.   } else { 
  14.     // Convert it to a string. 
  15.     return Object.prototype.toString.call(value); 
  16.   } 

关于TypeScript类型的说明

如果你已经在用 TypeScript,可能会惊讶地发现,TypeScript对 JSON.stringify的官方定义在这里并不正确。它们实际上看起来像这样:

  1. // Note: 这里面简化过 
  2. interface JSON { 
  3.   // ... 
  4.   stringify(value: any): string; 

不幸的是,这是一个长期存在的问题,没有一个完美的解决方案。

你可以尝试修补 JSON.stringify 的类型,但每个解决方案都有一定的缺点。我建议用自定义类型定义自己的包装器并。例如,Signal Desktop的reallyJsonStringify 的模板:

  1. function reallyJsonStringify(value: unknown): string { 
  2.   // ... 

总结

  • JSON.stringify 有时会返回 undefined,而不是一个字符串
  • JSON.stringify 有时会抛出一个错误
  • 我们可以通过用不同的方式包装函数来解决这个问题

希望这篇文章能让你对 JSON.stringify 有更全面的了解。

作者:BlackLivesMatter 译者:前端小智

来源:devinduct 原文:https://evanhahn.com/when-stringify-doesnt-return-a-string

 

责任编辑:姜华 来源: 大迁世界
相关推荐

2024-03-25 00:10:00

JSON后端开发

2021-05-06 05:30:33

JSONstringify()parse()

2023-01-17 16:25:18

前端开发JSON

2019-06-11 15:25:03

JSON性能前端

2021-12-11 18:59:35

JavascriptJSON应用

2020-06-12 09:20:33

前端Blob字符串

2020-07-28 08:26:34

WebSocket浏览器

2022-12-05 14:50:53

2020-03-29 20:16:09

JavaScript前端技术

2020-05-25 14:37:31

JSON.string前端秘密特性

2011-09-15 17:10:41

2009-12-10 09:37:43

2022-10-13 11:48:37

Web共享机制操作系统

2021-02-01 23:23:39

FiddlerCharlesWeb

2024-09-30 11:08:18

JSON局限性数据

2010-08-23 09:56:09

Java性能监控

2022-08-31 22:50:13

JavaScript函数JSON

2022-03-10 09:11:33

JavaScrip开发JSON

2020-09-15 08:35:57

TypeScript JavaScript类型

2022-11-04 08:19:18

gRPC框架项目
点赞
收藏

51CTO技术栈公众号