【问题标题】:Why is JSON.stringify not serializing prototype values?为什么 JSON.stringify 不序列化原型值?
【发布时间】:2012-09-04 08:57:57
【问题描述】:

我最近在 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 == { "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)

【问题讨论】:

  • 我知道方法不能通过 JSON 进行序列化,但感谢 lonesomeday,因为您指出我对此进行了讨论。显然这是一个记录在案的“问题”,不知道为什么我的搜索没有提出这个问题。谢谢!
  • @lonesomeday,我个人更喜欢参考标准,说“不,继承的属性不算数”,而不是回答“就是这样”。哦,在我输入此评论后,这就是答案。 :)

标签: javascript json node.js prototype


【解决方案1】:

仅仅是因为这是 JSON 的工作方式。来自ES5 spec:

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

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

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

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

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

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

【讨论】:

  • 谢谢詹姆斯,我想这可以解决这个问题。羞耻使原型在我的情况下基本上没用!那么将不得不求助于臃肿的构造函数!
  • @killercowuk - 您可以为您的对象提出自己的字符串化格式。我已经编辑了我的答案,以包括其原因背后的一些进一步的理由。
  • 干杯詹姆斯,我确实理解了理性,但是缩写 JSON - JavaScript Object Notation 感觉有点错卖;但是完全理解它本质上是一种“轻量级数据交换格式”(json.org)
【解决方案2】:

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

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

来自ES5 spec:

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

    一个。设 K 为 PropertyList。

  2. 其他

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

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

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

【讨论】:

  • 对不起,我没有早点看到这个,谢谢。这实际上确实提供了一种实现我最初要求的方法(几年后:))。我将使用解决方案编辑上面的答案。
【解决方案3】:

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

从 JSON 规范开始(15.12.3 stringify 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 字符串化函数。一般的实现可能是:

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”时,您需要小心使哪些属性成为可枚举或隐式可枚举。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-24
    • 2012-09-09
    • 2015-07-14
    • 2011-03-17
    • 1970-01-01
    • 2017-07-17
    相关资源
    最近更新 更多