为什么JSON.stringify没有序列化原型值

Why is JSON.stringify not serializing prototype values?

本文关键字:序列化 原型 JSON stringify 为什么      更新时间:2023-09-26

我最近一直在Node.js和浏览器中进行一些JSON解析和Javascript传递,但遇到了这个难题。

我使用构造函数创建的任何对象都不能通过JSON.stringify完全序列化,除非我单独初始化了构造函数中的所有值!这意味着我的原型在设计这些类时变得毫无用处。

有人能解释一下为什么以下内容没有像我预期的那样序列化吗?

var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a);

发生了什么:

a_string=={"initializedValue":"你可以看到我!"}

我希望:

a_string=={"initializedValue":"你能看到我!","uninitializedvalue":"看不到我!"}


更新(01-10-2019):

最后注意到@ncardeli的答案,它确实允许我们做以下事情来实现我的上述要求(在2019年!):

替换var a_string = JSON.stringify(a);

var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));

完整代码:

var ClassA = function () { this.initialisedValue = "You can see me!" };
ClassA.prototype = { initialisedValue : "You can't see me!", uninitialisedValue : "You can't see me!" };
var a = new ClassA();
var a_string = JSON.stringify(a, Object.keys(ClassA.prototype));
console.log(a_string)

因为这就是JSON的工作方式。来自ES5规范:

设K是一个内部字符串列表,由[[Enumerable]]属性为true的值的所有自身属性的名称组成。

这是有道理的,因为JSON规范中没有保存信息的机制,如果包含继承的属性,则需要将JSON字符串解析回JavaScript对象。在您的示例中,这将如何解析:

{"initializedValue":"你能看到我!","uninitializedValue:"你看不到我!}

除了具有2个键值对的平面对象之外,没有任何信息可以将其解析为其他内容。

仔细想想,JSON并不打算直接映射到JavaScript对象。其他语言必须能够将JSON字符串解析为名称-值对的简单结构。如果JSON字符串包含序列化完整JavaScript范围链所需的所有信息,那么其他语言可能无法将其解析为有用的信息。用Douglas Crockford在json.org上的话来说:

这些[哈希表和数组]是通用的数据结构。几乎所有现代编程语言都以这样或那样的形式支持它们。与编程语言可互换的数据格式也基于这些结构,这是有道理的。

我想补充一点,即使JSON.stringify只会字符串化对象自己的属性,正如接受的答案中所解释的那样,您也可以通过指定String的数组作为JSON.stringify的第二个参数(称为替换数组)来更改字符串化过程的行为。

如果您指定一个String数组,其中包含要字符串化的属性白名单,则字符串化算法将改变其行为,并将考虑原型链中的属性。

来自ES5规范:

  1. 如果PropertyList未定义,则

    a。设K为PropertyList。

  2. 其他

    a。设K是一个内部字符串列表,由其[[Enumerable]]属性为是的。字符串的顺序应与Object.keys标准内置函数。

如果你事先知道要字符串化的对象的属性的名称,你可以这样做:

var a_string = JSON.stringify(a, ["initialisedValue", "uninitialisedValue"]);
//  a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }

还有另一种可能性,仍然可以将原型中的属性字符串化。

截至JSON规范(15.12.3字符串http://es5.github.io/#x15.12.3):

[...]
2. If Type(value) is Object, then
    a. Let toJSON be the result of calling the [[Get]] internal method of value with argument "toJSON".
    b. If IsCallable(toJSON) is true
        i. Let value be the result of calling the [[Call]] internal method of toJSON passing value as the this value and with an argument list consisting of key.
[...]

因此,yu可以编写自己的JSON字符串生成器函数。一般的实现方式可以是:

class Cat {
    constructor(age) {
        this.age = age;
    }
    get callSound() {
        // This does not work as this getter-property is not enumerable
        return "Meow";
    }
    toJSON() {
        const jsonObj = {}
        const self = this; // If you can use arrow functions just use 'this'-keyword.
        // Object.keys will list all 'enumerable' properties
        // First we look at all own properties of 'this'
        Object.keys(this).forEach(function(k) {
            jsonObj[k] = self[k];
        });
        // Then we look at all own properties of this's 'prototype'
        Object.keys(Object.getPrototypeOf(this)).forEach(function(k) {
            jsonObj[k] = self[k];
        });
        return JSON.stringify(jsonObj);
    }
}
Object.defineProperty(Cat.prototype, 'callSound2', {
    // The 'enumerable: true' is important!
    value: "MeowMeow", enumerable: true
});

let aCat = new Cat(4);
console.log(JSON.stringify(aCat));
// prints "{'"age'":4,'"callSound2'":'"MeowMeow'"}"

只要正确的属性(那些你真正想被JSON字符串化的属性)是可枚举的,而那些你不想被字符串化的,这就可以了。因此,当您为"this"赋值时,您需要小心哪些属性是可枚举的或是隐式可枚举的。

另一种可能性是逐个手动分配您实际想要字符串化的每个属性。这可能不太容易出错。