16

最近,我一直在 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" : "你看不到我!" }


更新(2019 年 1 月 10 日):

最后注意到@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)
4

3 回答 3

16

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

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

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

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

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

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

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

于 2012-09-11T12:24:33.057 回答
6

我想补充一点,即使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!" }
于 2017-10-23T17:07:24.650 回答
0

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

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

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

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

于 2019-10-01T14:09:16.087 回答