Storing type info within classes
Issue #6 got me thinking about something else interesting--maybe we could have a Typeson option to check all objects for a "typeson" global symbol-keyed object (containing "replace" and "revive" methods) and use them (with probably lower priority than the registered types), so that the classes themselves could indicate how they could be serialized and revived. (There could be a "test" method, but it'd probably make more sense to check for the standard Symbol.hasInstance.)
But instead of storing the objects to be made available upon revival in a map, the user could pass in the classes (which would need to have both instance and static, symbol-keyed methods, the former for testing/replacing and the latter especially for revival). Or we could just have a typeson-registry type which did all this.
In either case, the app would need to be made aware of what classes to look for (e.g., new Typeson().registerClasses([MyClass1, MyClass2]) or new Typeson().register(generateClassType([MyClass1, MyClass2]))). This would look like MyClass1[Symbol.for('typeson')] = {replace (obj) {}, revive (serialized) {}}; and within the class perhaps as [Symbol.for('typeson-replace')] () {return serialize(this);}. Putting the type info on the class would ensure that the class serialization/deserialization was kept up to date with changes within the rest of the class and the modularization would help avoid the need for always looking to an external registry to find out how to serialize/deserialize.
Maybe the symbol key could even be dynamic, e.g., new Typeson().registerClasses([MyClass1, MyClass2], '[Serializable]') to get the Structured Cloning Algorithm for a class, so that there need not be only one way to store and retrieve serialization information on a class.
Another possible option would be for the serialization process to look for a custom method or perhaps the standard Symbol.toStringTag on the object so as to include the class name within the serialization and then the name could then used upon revival to find a class with that global name, an advantage of this being that the classes would not even need to be passed in upon revival so that globals could be avoided. However, besides necessitating globals, there'd be a security risk, so perhaps such revival could be opt in like this: new Typeson().registerClasses({MyClass1, MyClass2}, {checkGlobalsForMissingClasses: true}), with the other optional classes (keyed here by name) could be used to force revival with a particular class. There might even be some use cases for swapping implementations so that a serialization built using one class implementation could be deserialized in a different manner with another.