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

IT技术 javascript json
2021-01-14 10:59:06

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个回答

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

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

这个问题比乍一看要深一些。即使真的a将字符串化为{"position":0, "someVal":50},稍后解析它也会创建一个具有所需属性的对象,但它既不是 Actor 的实例,也不是 WorldObject 的原型链接(毕竟 parse 方法没有这个信息,因此它不可能以这种方式恢复它)。

为了保留原型链,需要一些巧妙的技巧(就像在 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会保持不变。

使用此代码会给我一条错误消息,因为其中一个属性是只读的。
2021-03-28 10:59:06
我成功地将其替换为 function flattenObject(obj) { var result = {}; for(var key in obj) { result[key] = obj[key]; } return result; }
2021-03-31 10:59:06
我认为这段代码基本上是我需要的。我应该能够保存这个序列化版本并编写一个简单的加载程序。干得好!
2021-04-11 10:59:06

另一种选择是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.stringifytoJSON在尝试本机序列化之前在它接收的对象中搜索方法。

检查这个小提琴: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);
}
这会改变原始对象,这对于通用答案来说真的很糟糕。
2021-03-25 10:59:06
我希望我可以将两者都标记为答案,因为这也与 Tomas 的片段一样有效,谢谢!
2021-03-28 10:59:06
@Mörre - 确实如此。这是很久以前的事了,在写这个答案时并没有真正想到这一点。:)
2021-04-13 10:59:06

这是他的答案中包含的片段@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)));

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

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