如何将继承的对象字符串化为JSON?

时间:2012-01-08 16:41:29

标签: javascript json

json2.js在使用JSON.stringify()时似乎忽略了父对象的成员。例如:

require('./json2.js');

function WorldObject(type) {    
    this.position = 4;
}

function Actor(val) {
    this.someVal = 50;
}

Actor.prototype = new WorldObject();

var a = new Actor(2);

console.log(a.position);
console.log(JSON.stringify(a));

输出结果为:

4
{"someVal":50}

我希望这个输出:

4
{"position":0, "someVal":50}

6 个答案:

答案 0 :(得分:34)

那就是它的方式,JSON.stringify不保留对象的任何非拥有属性。您可以查看有关其他缺点和可能的解决方法的有趣讨论here

另请注意,作者不仅记录了问题,还编写了一个名为HydrateJS的库,可能对您有帮助。

这个问题比第一眼看上去更深一些。即使a确实将字符串化为{"position":0, "someVal":50},稍后解析它也会创建一个具有所需属性的对象,但既不是Actor的实例,也不是与WorldObject的原型链接(之后)所有,解析方法没有这个信息,所以它不可能那样恢复。)

为了保留原型链,需要巧妙的技巧(如HydrateJS中使用的那些)。如果这不是您的目标,也许您只需要在对其进行字符串化之前“展平”该对象。要做到这一点,你可以例如迭代对象的所有属性,无论它们是否属于它们并重新分配它们(这将确保它们在对象本身上定义而不是仅从原型继承)。

function flatten(obj) {
    var result = Object.create(obj);
    for(var key in result) {
        result[key] = result[key];
    }
    return result;
}

写入函数的方式不会改变原始对象。所以使用

console.log(JSON.stringify(flatten(a)));

您将获得所需的输出,a将保持不变。

答案 1 :(得分:21)

另一种选择是在要序列化的对象原型中定义toJSON方法:

function Test(){}

Test.prototype = {

    someProperty: "some value", 

    toJSON: function() {
        var tmp = {};

        for(var key in this) {
            if(typeof this[key] !== 'function')
                tmp[key] = this[key];
        }

        return tmp;
    }
};

var t = new Test;

JSON.stringify(t); // returns "{"someProperty" : "some value"}"

这是有效的,因为在尝试本机序列化之前,JSON.stringify在它接收的对象中搜索toJSON方法。

答案 2 :(得分:9)

检查这个小提琴:http://jsfiddle.net/AEGYG/

您可以使用此功能对对象进行扁平化:

function flatStringify(x) {
    for(var i in x) {
        if(!x.hasOwnProperty(i)) {
            // weird as it might seem, this actually does the trick! - adds parent property to self
            x[i] = x[i];
        }
    }
    return JSON.stringify(x);
}

答案 3 :(得分:3)

以下是他的答案中包含的片段@TomasVana的递归版本,以防对象树的多个级别中存在继承:

var flatten = function(obj) {
    if (obj === null) {
        return null;
    }

    if (Array.isArray(obj)) {
        var newObj = [];
        for (var i = 0; i < obj.length; i++) {
            if (typeof obj[i] === 'object') {
                newObj.push(flatten(obj[i]));
            }
            else {
                newObj.push(obj[i]);
            }
        }
        return newObj;
    }

    var result = Object.create(obj);
    for(var key in result) {
        if (typeof result[key] === 'object') {
            result[key] = flatten(result[key]);
        }
        else {
            result[key] = result[key];
        }
    }
    return result;
}

它将数组保持为数组。以同样的方式调用它:

console.log(JSON.stringify(flatten(visualDataViews)));

答案 4 :(得分:1)

虽然flatten方法一般有用,但到目前为止发布的其他答案中的摘要不适用于不可修改的属性,例如原型是frozen。要处理这种情况,您需要创建一个新对象并将属性分配给此新对象。由于您只是对结果对象进行字符串化,因此对象标识和其他JavaScript内部结构可能并不重要,因此返回新对象完全没问题。这种方法也可以说比将对象的属性重新分配给自己更具可读性,因为它看起来不像是无操作:

function flatten(obj) {
    var ret = {};
    for (var i in obj) {
        ret[i] = obj[i];
    }
    return ret;
}

答案 5 :(得分:0)

JSON.stringify takes three options

JSON.stringify(value[, replacer[, space]])

因此,请使用replacer这个函数,它对每个key-value对都递归调用。

下一个问题,要真正获得所有内容,您需要follow the prototpes,并且必须使用getOwnPropertyNames来获取所有属性名称(比起keys或{{1} }):

for…in

然后将对象展平为JSON:

var getAllPropertyNames = () => {
  const seen = new WeakSet();
  return (obj) => {
    let props = [];
    do {
      if (seen.has(obj)) return [];
      seen.add(obj);
      Object.getOwnPropertyNames(obj).forEach((prop) => {
        if (props.indexOf(prop) === -1) props.push(prop);
      });
    } while ((obj = Object.getPrototypeOf(obj)));
    return props;
  };
};
var flatten = () => {
  const seen = new WeakSet();
  const getPropertyNames = getAllPropertyNames();
  return (key, value) => {
    if (value !== null && typeof value === "object") {
      if (seen.has(value)) return;
      seen.add(value);
      let result = {};
      getPropertyNames(value).forEach((k) => (result[k] = value[k]));
      return result;
    }
    return value;
  };
};

注意:

  • 我遇到过一个例子,值是JSON.stringify(myValue, flatten()); ,但是nulltypeof value
  • Circular references必须被检测到,因此需要"object"