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

时间:2012-09-11 12:18:21

标签: javascript json node.js prototype

我最近一直在使用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”:“你看不到我!” }

4 个答案:

答案 0 :(得分:13)

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

  

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

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

  

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

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

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

  

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

答案 1 :(得分:0)

我在使用mongoose时登陆此页面:如果其他用户遇到类似问题,则以下问题(以及相关答案)可能会有用:

How do you turn a Mongoose document into a plain object?

(基本上:使用Model' toObject()方法)

答案 2 :(得分:0)

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

如果指定一个JSON.stringify数组,其中包含属性的白名单以进行字符串化,则字符串化算法将更改其行为,并将考虑原型链中的属性。

来自ES5 spec

  
      
  1. 如果PropertyList未定义,则

         

    一个。设K为PropertyList。

  2.   
  3. 否则

         

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

  4.   

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

String

答案 3 :(得分:0)

还有一种可能性可以保留原型中待定义的属性。

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

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