Have you ever transferred objects across the JavaScript boundary? e.g.
- Backend to frontend, and vice versa
- Main thread to worker thread
- Electron's main to renderer process
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.