Javascript 在Node.js中反序列化后将对象与其类重新关联

Javascript 在Node.js中反序列化后将对象与其类重新关联,javascript,json,node.js,class,serialization,Javascript,Json,Node.js,Class,Serialization,我正在为一些特定于应用程序的对象编写一个简单的序列化/反序列化框架 考虑以下几点: "use strict"; function Dog(name) { this._name = name; }; Dog.prototype.constructor = Dog; Dog.prototype.getName = function() { return this._name; } var d1 = new Dog('fido'); var d2 = JSON.parse(JSON.stringif

我正在为一些特定于应用程序的对象编写一个简单的序列化/反序列化框架

考虑以下几点:

"use strict";
function Dog(name) { this._name = name; };
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() { return this._name; }

var d1 = new Dog('fido');
var d2 = JSON.parse(JSON.stringify(d1));  // serialize / deserialize

> d1
Dog { _name: 'fido' }
> d1.getName()
'fido'
> d2
{ _name: 'fido' }
> d2.getName()
TypeError: d2.getName is not a function
在这一点上,你可以问“什么是
d1
d2
缺少的?”

部分有效的一种方法是手动分配d1至d2的方法:

> d2.constructor = d1.constructor
> d2.getName = d1.getName
> d2.getName()
'fido'
这有几个缺点。首先,我必须手动将d1的每个方法分配给d2。其次,d2有自己的属性,不使用原型机制共享插槽:

> d2
Dog {
   _name: 'fido',
  constructor: [Function: Dog],
  getName: [Function] }

因此,我的问题是:给定一个对象(例如,
d2
),是否有办法将其与另一个对象(例如,
d1
)的原型相关联,从而继承相同的行为?

在撰写本文时,我想到了创建一个自定义构造函数,该构造函数使用反序列化的JSON来初始化对象:

Dog.createFromJSON = function(obj) {
  var d = new Dog();
  Object.keys(obj).forEach(function(key) {
    d[key] = obj[key];
  });
  return d;
}

> d3 = Dog.createFromJSON(JSON.parse(JSON.serialize(d1)))
> d3
Dog { _name: 'fido' }
> d3.getName()
'fido'
更新:如何动态查找类并分配原型 正如@Louis指出的,@Gothdo的答案要求您知道反序列化对象属于哪个类。如果愿意将类名添加到序列化对象中,可以使用该名称动态确定类。例如,要扩展OP的示例:

> var d1 = new Dog('fido');
> d1['_class'] = 'Dog';
> let jsonString = JSON.stringify(d1)
'{"_name":"fido","_class":"Dog"}'
使用中描述的技巧(但对Node.js进行了调整),您可以使用字符串通过Node.js的
全局
对象获取类原型的句柄:

> global[d1['_class']].prototype
Dog { getName: [Function] }
现在,您可以使用@Gothdo的技术动态地重建对象。总而言之:

/**
 * Dynamically create an object from a JSON string of properties.
 * Assumes the presence of a _class meta-property that names the
 * resulting class.
 */
function reconstitute(jsonString) {
    let obj = JSON.parse(jsonString);
    let cls = global[obj['_class']];
    delete obj['_class'];  // remove meta-property
    return Object.setPrototypeOf(obj, cls.prototype);
}

> reconstitute('{"_name":"fido","_class":"Dog"}')
Dog { _name: 'fido' }
这就是你需要的

const obj = JSON.parse(JSON.stringify(d1))
const d3 = Object.create(Dog.prototype, Object.getOwnPropertyDescriptors(obj))
此方法与OP方法的区别在于,此方法在原型上设置
prototype
属性,而OP方法直接在对象上设置属性。使用for in-loop和
hasOwnProperty()
方法循环对象自身属性时,可以看到这一点:

for (const i in d1) {
  if (d3.hasOwnProperty(i)) {
    console.log(i)
  }
}
使用我的方法,它只输出
\u name
,但使用OP的方法,它也输出
getName

不幸的是,
Object.getOwnPropertyDescriptors()
是ECMAScript 2017的一部分,目前仅在Firefox中支持,因此您需要使用Babel


或者,您可以使用。它比
Object.getOwnPropertyDescriptors()
具有更好的浏览器支持,但MDN不支持它,因为它速度慢

const d3 = JSON.parse(JSON.stringify(d1))
Object.setPrototypeOf(d3, Dog.prototype)

简单方法:就地更换班级

如果确实需要更改对象的类,此函数将在定义了
object.getPrototypeOf
object.setPrototypeOf
的任何系统上运行:

//将对象的类设置为`name`
函数setObjectClassName(obj,名称){
让newObj=eval('new'+name+'()');
设proto=Object.getPrototypeOf(newObj);
setPrototypeOf(obj,proto);
返回obj;
}
使用
JSON.serialize()
JSON.parse()
的示例:

class MyClass扩展对象{}
让original=newmyclass();
original.foo=“bar”;
console.log(original.constructor.name,original);
//MyClass{“foo”:“bar}
让originalClassName=origin.constructor.name;
让serialized=JSON.stringify(原始);
log(serialized.constructor.name,serialized);
//字符串“{“foo”:“bar”}”
让restored=JSON.parse(序列化);
console.log(restored.constructor.name,restored);
//对象{foo:'bar'}
restored=setObjectClassName(restored,originalClassName);
console.log(restored.constructor.name,restored);
//MyClass{foo:'bar'}
更好的方法:复制对象

Mozilla警告不要更改现有对象的原型,因为它是:

每个浏览器和JavaScript引擎中的操作都非常缓慢 -

如果您不一定需要就地更改,此函数将复制对象并更改副本的类:

函数copyObjectAndChangeClass(obj,名称){
让newObj=eval('new'+name+'()');
Object.assign(newObj,obj);
返回newObj;
}

答案清晰-谢谢。如果Object.setPrototypeOf()太慢,那么如果getOwnPropertyDescriptors不可用,则可以简单地多填充getOwnPropertyDescriptors。谢谢你的指导……真是太感谢你了。这个答案即使在嵌套类上也适用。然而,只是好奇它是如何引用内部类的?Meta comment:虽然这似乎是一个重复的问题,但中给出的答案并不适合node.js环境。这里给出的答案要好得多。那它还是复制品吗?是的,它还是复制品。这里给出的答案也可以移到另一个问题。@Louis但是如果这个问题更好,那么这个问题应该作为这个问题的副本来结束。我看了两个问题,并不认为这个问题明显比另一个好。@Louis但是这里的答案肯定更好。另一个问题的答案在Node.js中不起作用。在某些情况下,它不起作用,比如。另外,这两个问题稍有不同-在这篇文章中有一个类的引用,而在另一篇文章中只有类的名称作为字符串。因此,阅读你的评论和答案,我的老问题:不能被标记为重复,首先是因为它比这个问题早5年,因为这两者都是在不同的场景中描述的。
global[d1[''u class']]。prototype
允许(通过设计)访问所有内容。仅用于反序列化受信任的输入,或在
\u class
上添加一个筛选器,以仅允许反序列化预期的类如果输入不是来自受信任的源,
eval
是一个巨大的安全漏洞。