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

How to stringify inherited objects to JSON?

本文关键字:字符串 JSON 对象 继承      更新时间:2023-09-26

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}

事实就是这样,JSON.stringify不保留对象的任何非所有属性。你可以在这里看到一个关于其他缺点和可能的解决方案的有趣讨论。

还要注意,作者不仅记录了这些问题,还编写了一个名为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将保持不变。

另一个选项是在要序列化的对象原型中定义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方法。

检查这个小提琴: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);
}

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

JSON.stringify采用三个选项

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

所以,使用replacer,它是一个函数,对于每个key-value-对都会递归调用。

下一个问题是,要真正获得所有内容,您需要遵循prototpes,并且必须使用getOwnPropertyNames来获得所有属性名称(比使用keysfor…in可以获得的更多):

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:

JSON.stringify(myValue, flatten());

注:

  • 我有一个例子,值是null,但typeof value"object"
  • 必须检测循环引用,因此需要seen