为什么JSON.stringify()会出现以下问题呢?

yyds
  • 15

1、如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
3、如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
6、如果对象中存在循环引用的情况也无法正确实现深拷贝。

这1-6都是因为什么原因,才会得到这样的结果呢?

回复
阅读 2.4k
4 个回答

你所有问题的答案都是规定如此(规定见图):

image.png

https://developer.mozilla.org...

其实就是 JSON 支持的数据类型跟 JS 比那是天差地别的,要少太多了,具体有哪些看文档:https://developer.mozilla.org...


【补充】

楼下说是因为 JSON 的设计初衷的轻量级的缘故,这个说法倒是没问题,说白了就是设计者当时没考虑那么多。

其实不需要扩展额外的语法,只需要包装一下完全能解决问题,比如:

{
   "foo": {
       "$type": "datetime",
       "$value": "2021-09-08T04:08:57.829Z"
   }
}

如果某些语言的标准库没有类似 Date 的数据结构,就 Fallback 退化成普通的 Object 处理,这也完全没有什么问题。

你要非说这么做就“不轻”了,那没有问题;但真的很“重”吗?

P.S. 其实社区已经有扩展性更好的"JSON"了,比如 BSON,同样也是标准化的序列化方案。各个语言也都有丰富且良好的实现,只不过 JS 没内置 API、需要你引入第三方库罢了。

这些问题的根源与JSON的设计初衷有关。JSON的设计目的是成为一种通用的、轻量级的数据交互格式,因此不能只考虑JavaScript,还需要考虑别的编程语言;同时,也不能给这一格式增加太多的语义规则,避免它变得臃肿复杂。

JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. It is based on a subset of the JavaScript Programming Language Standard ECMA-262 3rd Edition - December 1999. JSON is a text format that is completely language independent but uses conventions that are familiar to programmers of the C-family of languages -- https://www.json.org/

具体而言:

1、如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。

原因在于JSON中没有描述“时间对象”的语义,因此在JSON.stringify(obj)阶段就已经丢失了“时间对象”这一信息,转而用普通的字符串来对时间进行表示。至于为什么JSON中没有描述“时间对象”的语义,可以进行如下推测:

  • 无法保证所有的编程语言都内置Date对象,更无法保证所有的编程语言中内置的Date对象行为统一。这种不一致的表现对于一种数据交换格式来说是致命的。
  • 若引入“时间对象”,则JSON中势必需要引入特殊的key规则或者特殊的value规则,用于声明某个值的类型为“时间”,这严重地违背了“轻量级”的设计初衷。
2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。

原因同上。无法保证所有的编程语言都内置RegExp和Error对象,更无法保证所有的编程语言中内置的这些对象行为统一。

3、如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。

原因同上。但对于函数而言还存在另一个更重要的考虑:序列化函数、传输序列化结果、反序列化成为新的函数对象,这一过程的本质是传输一段可执行逻辑,而这在不安全的网络世界里是极为危险的。

4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。

原因同上。很多编程语言中都没有这些特殊值。

5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。

原因同上。若需在反序列化过程中保留对象的类信息,则必须在序列化过程中将这些信息也进行保留,而保留的这些信息只能用于JavaScript,无法供其它编程语言解析。

6、如果对象中存在循环引用的情况也无法正确实现深拷贝。

原因在于JSON中并没有对循环引用进行表示的语义。而为何如此?我的理解是“非不能也,实不为也” -- 增加对循环引用的语义表示将大大提高JSON规则的复杂度。

话虽如此,当序列化阶段使用的是JavaScript,而反序列化阶段使用的也是JavaScript时,上述问题一定程度上就成为JSON的局限了。为了解决这一问题,我开发了一个小工具:https://www.npmjs.com/package/esserializer。该工具解决的问题有:

  • 支持内置对象和类型:Boolean, Date, Infinity, NaN, undefined
  • 序列化/反序列化过程中保留对象的类和继承信息
  • 支持循环引用的回环结构

stringify有第二个参数,可以处理一些特殊类型比如function

  JSON.stringify(obj,function(key,value){
    if(typeof value == 'function'){
        return `${value}`//或者value.toString
    }
    return value
})

然后prase这样写

JSON.prase(obj,function(key,value){
    return eval(value);
})

还有就是,如果对象父子级循环引用,stringify就gg了:)

除了大佬们的专业释义外,我觉得还可以去了解一下序列化与反序列化

代码层面的结构要转换成一个与代码不相关的格式,相互转化都是需要遵循一些规则的。没有规则想怎么转就怎么转,就失去了作为传递信息载体的作用

JSON全名JavaScript Object Notation,JavaScript对象表示法,既然是表示法,那就得具备语义明确以及通用的特性,上面那些规则如果没有明确,就没法成为一个规则。

虽然叫JavaScript对象表示法,但是现在其他语言也都支持序列化为JSON字符串,不同的语言之间差异巨大,为了通用求同存异是最好的做法。像NaN和Infinity这种很多语言不存在的特殊值,只好用一个更加通用,范畴更大的null来表达

你知道吗?

宣传栏