I made some assumptions while making this change, and am very open to feedback. I've tested the Union functionality pretty thoroughly by running my external project's test suite against it, so I'm fairly confident that things work in general, but because of my inexperience with jms/serializer there may be many interactions that I've missed.
This adds Union support for objects in 2 ways:
Discriminated Unions
This is the easy case. For objects that have a field which specifies their type, i.e.:
For more complex cases, the UnionDiscriminator Annotation can also receive a map attribute, which will map from the values contained in the discriminator field, to the fully qualified class.
@Type annotations don't support unions (I looked into modifying the lexer/parser, but it's pretty non trivial to add).
There's no support for "top level" deserialization for discriminated unions, because there's no way to annotate the call to ->deserialize. i.e. they are only supported when they are Properties on objects.
Non-Discriminated unions
There is no "correct way" to deserialize into Union Types without discriminated fields, so I borrowed an approach I've seen in other languages (I recently added a similar implementation to the Pythondataclasses_json package, and this approach is also similar to TypeScript's zod package).
Essentially, in order to deserialize a Union, I "try" to deserialize into the possible types one by one until I find a type that the data matches well, and I assume that is the correct type.
Step 1:
I Sort the objects based on the number of required properties on the object (descending from most required properties to least) primitives are always sorted first.
Step 2: Attempt to deserialize into each type
Deserialization may fail for a couple reasons:
If JSON contains data of a type that doesn't match the expected type from the Object.
If the JSON does not contain a required property from the Object (This behaviour is configurable in the UnionHandler constructor - but for resolving potential conflicts, it's essentially mandatory).
Notes:
If the JSON contains a field that the Object doesn't have a matching field for, then we just ignore that field - I don't treat that as a failure.
The bulk of the code supporting this change can be seen in the UnionHandler and the DerserializationGraphNavigator.
The enumeration of possible exceptions in UnionHandler seems like a point of fragility.
The introduction of requireAllRequiredProperties field on the JsonDeserializationVisitor is a bit peculiar - any feedback on a more idiomatic way to add this functionality is welcomed.
@scyzoryck @goetas - sorry that this PR is so big! I hope you'll agree it's worth the time to add.
This pull request is part two, building on #1546.
I made some assumptions while making this change, and am very open to feedback. I've tested the Union functionality pretty thoroughly by running my external project's test suite against it, so I'm fairly confident that things work in general, but because of my inexperience with
jms/serializer
there may be many interactions that I've missed.This adds Union support for objects in 2 ways:
This is the easy case. For objects that have a field which specifies their type, i.e.:
I added a new Annotation:
UnionDiscriminator
that allows you to use this field to determine which object to deserialize into.For more complex cases, the
UnionDiscriminator
Annotation can also receive amap
attribute, which will map from the values contained in the discriminator field, to the fully qualified class.i.e.
Known issues with Discriminated Unions:
@Type
annotations don't support unions (I looked into modifying the lexer/parser, but it's pretty non trivial to add).There's no support for "top level" deserialization for discriminated unions, because there's no way to annotate the call to
->deserialize
. i.e. they are only supported when they are Properties on objects.There is no "correct way" to deserialize into Union Types without discriminated fields, so I borrowed an approach I've seen in other languages (I recently added a similar implementation to the
Python
dataclasses_json
package, and this approach is also similar toTypeScript
'szod
package).Essentially, in order to deserialize a Union, I "try" to deserialize into the possible types one by one until I find a type that the data matches well, and I assume that is the correct type.
Step 1: I Sort the objects based on the number of required properties on the object (descending from most required properties to least) primitives are always sorted first.
Step 2: Attempt to deserialize into each type
Deserialization may fail for a couple reasons:
If JSON contains data of a type that doesn't match the expected type from the Object.
If the JSON does not contain a required property from the Object (This behaviour is configurable in the
UnionHandler
constructor - but for resolving potential conflicts, it's essentially mandatory).Notes: If the JSON contains a field that the Object doesn't have a matching field for, then we just ignore that field - I don't treat that as a failure.
The bulk of the code supporting this change can be seen in the
UnionHandler
and theDerserializationGraphNavigator
.The enumeration of possible exceptions in
UnionHandler
seems like a point of fragility.The introduction of
requireAllRequiredProperties
field on theJsonDeserializationVisitor
is a bit peculiar - any feedback on a more idiomatic way to add this functionality is welcomed.@scyzoryck @goetas - sorry that this PR is so big! I hope you'll agree it's worth the time to add.