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
是一个巨大的安全漏洞。