This article is a part of the systemic-ts series.
complication
complication

Source of Complication: Class/Prototype vs Serialization/Deserialization

Have you ever transferred objects across the JavaScript boundary? e.g.

If what you're transferring is a "class object", it will never work smoothly.

class, which uses prototype information under the hood is static. It comes with your program. Common sense says it must not be serialized and it is not serialized by default.

But in the permissive world of TypeScript and JavaScript, no one's stopping you from doing this:

const something = new SomeClass();
something.someMethod();
const serialized = JSON.stringify(something);
sendSomewhere(serialized);

You will hope that you can reverse it by deserializing it.

const serialized = receiveSomething();
const something = JSON.parse(serialized) as SomeClass; // expect this to be of SomeClass
something.someMethod(); // disappointed

But it turns out disappointing when you call someMethod and the method does not exist.

Those experienced enough know that this is bad practice, but it is common to expect serialization and deserialization should be reversible.

It may be partially caused by JSON's non-reversible nature (and this series will later visit JSON's non-reversible nature), but replace JSON.stringify and JSON.parse with any serialization/deserialization method and prototype and the result will just be the same.

Insisting on Using Class

class SomeClass {
  static serialize(someClass: SomeClass) {
    return JSON.stringify(someClass);
  }

  static deserialize(str: string) {
    const obj = JSON.parse(str);
    const instance = new SomeClass();
    Object.entries(obj).forEach(([key, val]) => {
      instance[key] = val;
    });
    // Or more horribly, using
    // Object.setPrototypeOf()
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf
    return instance;
  }
}

With this somewhat "better" serialization/deserialization pair, you can expect this to work more often:

const something = new SomeClass();
something.someMethod();
const serialized = SomeClass.serialize(something);
const somethingCopy = SomeClass.deserialize(serialized) as SomeClass; // expect this to be of SomeClass
something.someMethod(); // disappointed

But to what extent?

What if at the receiving end, they don't have the same deserialization method?

Must you write serialization and deserialization for every class you have? You have to write more as your codebase (i.e. the number of classes) grows.

Writing serialization and deserialization for every class just does not scale.

complication
complication