为什么 JSON.stringify 不序列化原型值?

IT技术 javascript json node.js prototype
2021-03-18 13:23:24

我最近一直在处理相当多的 JSON 解析和在 Node.js 和浏览器中传递 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 == { "initialisedValue" : "你可以看到我!" }

我希望:

a_string == { "initialisedValue" : "你能看到我!", "uninitialisedValue" : "你看不到我!" }


更新 (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)
3个回答

仅仅是因为这是 JSON 的工作方式。ES5 规范

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

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

{ "initialisedValue" : "你能看到我!", "uninitialisedValue" : "你看不到我!" }

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

如果你仔细想想,JSON 并不打算直接映射到 JavaScript 对象。其他语言必须能够将 JSON 字符串解析为名称-值对的简单结构。如果 JSON 字符串包含序列化完整 JavaScript 作用域链所需的所有信息,则其他语言可能无法将其解析为有用的内容。json.org上的道格拉斯·克罗克福德(Douglas Crockford) 的话来说

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

@killercowuk - 您可以为您的对象提出自己的字符串化格式。我已经编辑了我的答案,以在其原因背后包含一些进一步的理由。
2021-04-24 13:23:24
谢谢詹姆斯,我想这可以解决这个问题。可耻的是,在我的情况下,原型基本上毫无用处!那么将不得不求助于臃肿的构造函数!
2021-04-30 13:23:24
干杯詹姆斯,我确实明白了,但是缩写 JSON - JavaScript Object Notation 感觉有点误会;然而完全理解它从根本上说是一种“轻量级数据交换格式”(json.org)
2021-05-10 13:23:24

我想补充一点,尽管JSON.stringify只会对对象自己的属性进行字符串化,如已接受的答案中所述,您可以通过指定一个数组String作为JSON.stringify(称为替换器数组)的第二个参数来改变字符串化过程的行为.

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

ES5 规范

  1. 如果 PropertyList 不是未定义的,则

    一个。令 K 为 PropertyList。

  2. 别的

    一个。令 K 是一个内部字符串列表,由所有 [[Enumerable]] 属性为 true 的 value 自身属性的名称组成。字符串的顺序应该与 Object.keys 标准内置函数使用的顺序相同。

如果您事先知道要字符串化的对象的属性名称,则可以执行以下操作:

var a_string = JSON.stringify(a, ["initialisedValue", "uninitialisedValue"]);
//  a_string == { "initialisedValue" : "You can see me!", "uninitialisedValue" : "You can't see me!" }
抱歉没有早点看到,谢谢。这实际上确实提供了一种实现我最初要求的方法(几年后:))。我将使用解决方案编辑上面的答案。
2021-04-24 13:23:24

还有另一种可能性,仍然有来自原型的属性被字符串化。

从 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.
[...]

所以你可以编写自己的 JSON stringifier 函数。一般实现可能是:

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”时,您需要注意哪些属性是可枚举的或隐式可枚举的。

另一种可能性是一个一个地分配你真正想要手动字符串化的每个属性。这可能不太容易出错。