-
-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
discussion: support polymorphism - major change #65
Comments
I am open to definitely better supporting polymorphism but I don't think the way to do is through adding information to the serialized data. That means that moves serializr away from being a generic serialization library without a whole lot of added benefit I think, but doesn't necessarily solve the problem at hand. The issue you mention is that when serializing an array, it selected the first schema from the array to serialize. This is because when using an array of many things, the suggested route to take is to use a custom type. Using a custom type is what I use for polymorphism, and during serialization I just do
This is effectively the currently suggested workaround of your issue with the first item in the array being the default schema. In your example, you would replace the
line with the custom serializer to fix this issue. Back to the issue though, while I don't agree with adding metadata to the serialized version (whatever your data storage medium is shouldn't store information relative to a frontend library, as is suggested by proper separation of concerns), I would agree that I think we need some sort of polymorphism control due to the frequent requests for the feature. I've been doing some research as to how other serializers manage this situation, and I think I might be able to suggest an alternative, to start, in the form of a It would work like
It would work like.
or in plain JS
The upside of this implementation is that Lists wouldn't require any dev work outside of getting the default model schema for each individual item. As far as implementation for this, when the Do you have any thoughts on this @nros? @mweststrate ? |
Actually, I think my version is too specific already. I wonder if we'd be better off with a union type like from mobx state tree. Something like
|
union type seems insufficientThe union type has the disadvantage that exactly have to define the possible sub classes to use. Think of all those minecraft mods that add new items/blocks. So, this solution seems to fall short. "class identification information" needed in serialized dataIMHO It ends up to the point that additional information about the runtime type has to be added to the serialized data. Som sort of symbolic "identifier" not the class name itself (because of packers, etc.). It is used to find the proper schema upon deserialization. global lookup table neededAt the moment in combination with a dependency injection systemMy working solution - based on the solution as an extra type "anytype"To maintain current simplicity of
|
Then we need the "class identifier" with the class.
|
I really don't think storing any serialization info in the serialized data is a good idea. Imagine any of these scenarios:
See all the issues that adding metadata into serialization carries? That's why it's so important to have a With regard to your issue that the Union type that the user needs to define all the subclasses ahead of time, there's not really any other way to do it. I'm assuming you're not implying metaprogramming here, but when deserializing, it's the users job to specify how to deserialize the data. Can you explain how the union type wouldn't solve your array issue mentioned above? Or maybe explain a different use case it doesn't solve? |
Hm. I totally agree with you that it would be nice to have a solution that does not need to store meta-information with the serialized data. I see your points. Nonetheless, even the union type has to decide which of the types select when deserializing. Hence there must be a distinct feature available to distinguish between the types. Generally I like the idea of basing the type decision on already existing attributes. This should work well as long as entities can be distinguished by their data/attributes, which should be no problem with carefully crafted models. But instead of having a fixed list of possible types, I would prefer to use the "type decision function", like the lookup function with where union type might fall shortA fixed list of used types would make the system very inflexible. I try to explain why. Of course I may be wrong or not fully understand your concept. plugin systemNot knowing all types upfront might happen with a plugin system. I think of a module based system where a vendor (A) provide the base classes + UI + business logic (classical MVC) for an application. An example system might manage block-chains (another buzz-word). Each chain element has a content type. Other developers (B) base their product on it and may add more types as they needed. The content types make sense only to B. And since there are thousands of B's, A is reluctant to add all types a B is implementing to the union list. mods and enhancements by other developers/vendorsThe same applies to Minecraft. Each mod is able to add more block types with special behaviour but the mods are unable to change the code of the base Minecraft system. Since there are thousands of individual developers to small teams creating mods of mixed quality, Mojang would be unable to maintain a list of all added block types. There is no such registry. So there must be a different mechanism to store all blocks, including blocks of unknown type. reluctance to touch or read working codeI experienced in the past that there are not many developers willing to read code of others or even change such code. Fewer of these even want to understand "foreign" code. |
I think your criticisms of the Union type largely support the need for something as loosely coupled as the union type is. First, you can't serialize/deserialize something in serializr without first creating a schema for it. That means that regardless of what it is, the object has to get passed to serializr first, so then setting the expectation that the user pass all the types to a Union, is not unreasonable. In addition to that, if we wanted, we could probably get away not even passing in the types, and just passing in a functions. Second, as with Minecraft, the developers have to specify all the blocks they want to add. This is the same with Serializr, except instead of blocks, there's types. Minecraft can't guess what blocks the developers are going to add, just as serializr can't guess what types the developer wants to have. Maybe you could get away with doing without passing the blocks in though. Something like
After thinking about it, I don't really see a benefit to passing in a list of items, short of it being a "strict" whitelist, as opposed to a blacklist. What about something like that? |
Yep, I like that. This seems a good, reasonable way. It is simple, does not impose the need to build a global registry of all classes to |
There is another subject that is bothering me: references - do you want a separate discussion? When deserializing an entity and this entity has a identifier property I would like to try to find it in the JSON - much like
You see that entity "iu" is always completely serialized. But when deserialized, the list item Of course you might argue that there is a problem in case the ID match but the properties do not! I would ignore the following JSON data in case the ID (and the class type) match. It is impossible to decide which data is the correct one, so just use the first set of properties. The problem is, that What do you think? |
Hi, I was able to get polymorphism kind of working with the suggested custom type:
But if I have a reference to an item, it fails to resolve the reference. How could I specify the base class when resolving the reference? Encode something into the identifier? |
Actually I just realized the above does not work because the custom function doesn't have access to the parent deserialize context. It should really use |
this is the reason for my PR. see #67 |
I was able to get my issue working by exposing
Kind of ugly, but it works. It seems references automagically work if I just specify the base class:
|
I assume getClazz() looks up the class from the obj.type? @alexggordon I am not worried about e.g. the class name changing, because it is a given that if you are persisting the user's data you have to support backwards compatibility, i.e. you can never replace or remove existing classes, only add new ones and explicitly handle converting old ones to the new ones that replace them, if needed. |
Yeah getClazz is a maintained lookup for the class. I realize you are
talking about making things more automatic which is great. But this
poor-mans version could work fine for me if that one method was made
public. I had to fork and make it public.
…On Wed, Dec 6, 2017 at 12:35 AM, Kit Adams ***@***.***> wrote:
I assume getClazz() looks up the class from the obj.type?
Do you manually have to maintain that?
I would like to use a (class) decorator to maintain that dictionary.
@alexggordon <https://github.com/alexggordon> I am not worried about e.g.
the class name changing, because it is a given that if you are persisting
the user's data you have to support backwards compatibility, i.e. you can
never replace or remove existing classes, only add new ones and explicitly
handle converting old ones to the new ones that replace them, if needed.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#65 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ABuUTtraweRlIpWF17-TY18W0ntx3a-pks5s9ieLgaJpZM4QTQKy>
.
|
I enhanced the serializing code with some decorators to achieve persistence with clazz tags for polymorph items. Please have a look at this: @PolyMorph @serializable(polymorphArray(Animal)) private data: Array; |
Dear maintainers,
would you mind to support polymorphism when serializing?
Problem
At the moment the schema exactly defines the expected type of the property to serialize. If any subclass adds some more properties to serialize, these are simply ignored. Deserializing does not create the original type of instance but the schema-defined type.
Similar, arrays are serialized based on the first item only.
serializr/src/core/serialize.js
Line 24 in 59fe6e5
This behavior does not enable using polymorphism with sub classes and does not work well with the idea of dependency injection.
See this simple example:
The output is:
With the serialized version, all information about instances of
Test2
are gone.Possible solution
The schema for serializing is already stored with the instance constructor.
serializr/src/api/getDefaultModelSchema.js
Line 17 in 59fe6e5
Hence it would be possible to detect the schema of the instance to serialize dynamically. Additionally adding some sort of "class name" to the serialized information would be possible and would help to re-create the proper instance upon deserialization.
So the serialized info could look like:
The class name is a symbolic "class name identifier" (string), either be set by the defined schema or a decorator. This avoids problems with packers that scramble class names. When reading all schemas before deserialization, an internal map of "class name identifiers" to schemas can be build to find the schema for the "class name identifier". Already each schema contains a factory function that is able to create a new instance for that schema.
Do you mind?
Would you mind going that route and accept PRs implementing this?
May be someone is willing to help?
I cannot promise to, but will try.
The text was updated successfully, but these errors were encountered: