gabrielcsapo / json-ex

🧠 extends JSON to be able to serialize and deserialize more than just basic primitives
https://gabrielcsapo.github.io/json-ex
MIT License
2 stars 2 forks source link

[Proposal] Class instance plus ancestry #1

Open nyteshade opened 7 years ago

nyteshade commented 7 years ago

Objects that are instances of any given type, typically class instances, should also serialize their constructor chain back to ECMA script types; i.e. Function. For example

let instance;
class A {}
class B extends A {}
class C extends B {}

instance = new C();

function constructLineage(instance) {
  const lineage = [];

  for (
    let ancestor = instance.constructor; 
    ancestor !== Function.prototype; 
    ancestor = Reflect.getPrototypeOf(ancestor)
  ) {
    lineage.push(ancestor)
  }

  return lineage;
}

constructLineage(instance); // [ [Function: C], [Function: B], [Function: A] ]

Given the code above you have the lineage of a given class instance. In order to accurately rebuild this object in a different environment, you'll need to persist those classes (via toString()) and recreate them before recreating the object in question if the target environment does not already contain those object definitions.

Perhaps it'll end up being something like

{
  ...
  classInstance: [{instanceProp: 'value'}, [C.toString(), B.toString(), A.toString()]]
  ...
}

Where when parsed, C, B, and A will be recreated if necessary. It is arguable as to whether these need to be created globally or within an iffy or something of that nature to protect scope creep. Then afterwards follow up with a let instance = Object.create(C); Object.assign(instance, classInstance[0]) thereby creating a new instance of C with its prototype chain intact and then doing an Object.assign on top to copy the previous state of the object instance.

Afterwards the following should work

instance instanceof C // true
instance instanceof B // true
instance instanceof A // true

// the properties of instance and classInstance should match
// but instance !== classInstance

I may be missing some details here; namely Reflect.getPrototypeOf returns the prototype, not the constructor. There may be some tweaking needed here.

nyteshade commented 7 years ago

After a little research it may be sufficient to simply capture

JSONEx = {/* Your functions for JSONEx stuff */}

let instance = ...; // value to capture
let prototype = Reflect.getPrototypeOf(instance);
let capture = { 
  instance: JSONEx.stringify(instance), 
  prototype: JSONEx.stringify(prototype),
  constructor: prototype.constructor.toString()
};

// Reconstruction
function reconstruct(capture) {
  let newInstance = null;
  (() => {
    let _class = eval(`({capture.constructor})`);
    let _inst = JSONEx.parse(capture.instance);
    let _proto = JSONEx.parse(capture.prototype);
    newInstance = Object.create(_inst);
    Reflect.setPrototypeOf(newInstance, _proto); 
  })();
  return newInstance;
}

Or something of the like.

gabrielcsapo commented 7 years ago

thank you @nyteshade I will look into adding this functionality! Will update on any progress I make back to this thread.

dmikey commented 7 years ago

This is awesome +1