简体   繁体   English

在 Node.js 中反序列化后将 object 与其 class 重新关联

[英]Re-associating an object with its class after deserialization in Node.js

I'm writing a simple serialization / deserialization framework for some application-specific objects.我正在为一些特定于应用程序的对象编写一个简单的序列化/反序列化框架。

Consider the following:考虑以下:

"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

At this point, one can ask "What does d1 have that d2 lacks?"此时,可以问“ d1有什么d2缺少什么?”

One approach that partially works is to manually assign the methods of d1 to d2:一种部分有效的方法是手动将 d1 的方法分配给 d2:

> d2.constructor = d1.constructor
> d2.getName = d1.getName
> d2.getName()
'fido'

This has a couple of disadvantages.这有几个缺点。 First, I have to manually assign each method of d1 to d2.首先,我必须手动将 d1 的每个方法分配给 d2。 Second, d2 gets its own properties, and doesn't share slots using the prototype mechanism:其次,d2 获取自己的属性,并且不使用原型机制共享插槽:

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

So my refined question is: given an object (eg d2 ), is there a way to associate it with the prototype of another object (eg d1 ) so it inherits the same behavior?所以我提炼的问题是:给定一个 object(例如d2 ),有没有办法将它与另一个 object(例如d1 )的原型相关联,以便它继承相同的行为?

Object.create() and Object.getOwnPropertyDescriptors() is what you need. Object.create()Object.getOwnPropertyDescriptors()正是您所需要的。

const obj = JSON.parse(JSON.stringify(d1))
const d3 = Object.create(Dog.prototype, Object.getOwnPropertyDescriptors(obj))

The difference between this and OP's method is that this method sets prototype properties on the prototype, whereas OP's method sets properties directly on the object. this 和 OP 的方法的区别在于,该方法在原型上设置prototype属性,而 OP 的方法直接在对象上设置属性。 You can see this when you loop through object own properties using for-in loop with hasOwnProperty() method:当您使用带有hasOwnProperty()方法的 for-in 循环遍历对象自己的属性时,您可以看到这一点:

for (const i in d1) {
  if (d3.hasOwnProperty(i)) {
    console.log(i)
  }
}

With my method it outputs only _name , but with OP's method it outputs also getName .用我的方法它只输出_name ,但用 OP 的方法它也输出getName

Unfortunately, Object.getOwnPropertyDescriptors() is part of ECMAScript 2017 and it's supported only in Firefox for now, so you'll need to use Babel.不幸的是, Object.getOwnPropertyDescriptors()是 ECMAScript 2017 的一部分,目前仅在 Firefox 中受支持,因此您需要使用 Babel。


Alternatively, you can use Object.setPrototypeOf() .或者,您可以使用Object.setPrototypeOf() It has better browser support than Object.getOwnPropertyDescriptors() , but it's discouraged by MDN, because it's slow.它比Object.getOwnPropertyDescriptors()具有更好的浏览器支持,但 MDN 不鼓励它,因为它很慢。

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

As I was writing this, I had the idea of creating a custom constructor that uses the deserialized JSON to initialize the object:在我写这篇文章的时候,我想到了创建一个使用反序列化的 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'

Update: how to dynamically find the class and assign the prototype更新:如何动态查找类并分配原型

As @Louis points out, @Gothdo's answer requires that you know what class the deserialized object belongs to.正如@Louis 指出的那样,@Gothdo 的回答要求您知道反序列化对象属于哪个类。 If you're willing to add the class name to the serialized object, you can use that to determine the class dynamically.如果您愿意将类名添加到序列化对象中,您可以使用它来动态确定类。 So, for example, to expand on the OP's example:因此,例如,要扩展 OP 的示例:

> var d1 = new Dog('fido');
> d1['_class'] = 'Dog';
> let jsonString = JSON.stringify(d1)
'{"_name":"fido","_class":"Dog"}'

Using the trick described in deserialize JSON to JAVASCRIPT object (but tweaked for Node.js) you can use a string to get a handle to a class prototype via Node.js's global object:使用将 JSON 反序列化为 JAVASCRIPT 对象(但针对 Node.js 进行了调整)中描述的技巧,您可以使用字符串通过 Node.js 的global对象获取类原型的句柄:

> global[d1['_class']].prototype
Dog { getName: [Function] }

Now you can use that to dynamically reconstruct the object using @Gothdo's technique.现在您可以使用它来使用@Gothdo 的技术动态重建对象。 Putting it all together:把它们放在一起:

/**
 * 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' }

The Best method so far would be:到目前为止最好的方法是:

let obj = Object.assign(new ClassyObject(), JSON.parse(JSON.serialize(the_obj_that_will_lost_prototype)))

Just improved and more direct:刚刚改进和更直接:

let obj = Object.assign(new the_obj_that_will_lost_prototype.constructor(), JSON.parse(JSON.serialize(the_obj_that_will_lost_prototype)))

Simple Method: Change Class In-Place简单方法:就地更改类

If you really need to change the class of an object in-place , this function will work on any system that has Object.getPrototypeOf and Object.setPrototypeOf defined:如果你真的需要就地更改对象的类,这个函数将适用于任何定义了Object.getPrototypeOfObject.setPrototypeOf系统:

    // set class of object to `name`
    function setObjectClassName(obj, name) {
      let newObj = eval('new ' + name + '()');
      let proto = Object.getPrototypeOf(newObj);
      Object.setPrototypeOf(obj, proto);
      return obj;
    }

Example using JSON.serialize() and JSON.parse() :使用JSON.serialize()JSON.parse()示例:

    class MyClass extends Object {}
    let original = new MyClass();
    original.foo = "bar";

    console.log(original.constructor.name, original);
    //  MyClass { "foo": 'bar' }

    let originalClassName = original.constructor.name;

    let serialized = JSON.stringify(original);
    console.log(serialized.constructor.name, serialized);
    // String '{"foo":"bar"}'

    let restored = JSON.parse(serialized);
    console.log(restored.constructor.name, restored);
    // Object { foo: 'bar' }

    restored = setObjectClassName(restored, originalClassName);
    console.log(restored.constructor.name, restored);
    // MyClass { foo: 'bar' }

Better Method: Copy the Object更好的方法:复制对象

Mozilla warns against changing the prototype of an existing object, as it is: Mozilla警告不要更改现有对象的原型,因为它是:

a very slow operation in every browser and JavaScript engine - Mozilla在每个浏览器和 JavaScript 引擎中运行都很慢 - Mozilla

If you don't absolutely need to change in-place, this function will copy an object and change the class of the copy:如果您不是绝对需要就地更改,此函数将复制一个对象并更改副本的类:

  function copyObjectAndChangeClass(obj, name) {
    let newObj = eval('new ' + name + '()');
    Object.assign(newObj, obj);
    return newObj;
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM