-
-
Notifications
You must be signed in to change notification settings - Fork 586
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
Allow union deserialization using discriminator map #1553
Allow union deserialization using discriminator map #1553
Conversation
Hi @goetas - I wonder if you've had a chance to look at these changes? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hi, sorry for the delay in my review. i have left some comments, could you answer my questions please?
after you answer to #1553 (comment), could you please update the documentation as well? most of what you need is to update /doc/reference/annotations.rst
src/Handler/UnionHandler.php
Outdated
if (null !== $finalType && null !== $finalType['name']) { | ||
return $context->getNavigator()->accept($data, $finalType); | ||
} else { | ||
foreach ($type['params'] as $possibleType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something is odd with this foreach loops, you have here a nested loop the same variable (line 70 and 102). shouldn't they be independent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was an error - removing the inner loop is the correct fix.
src/Handler/UnionHandler.php
Outdated
} | ||
} else { | ||
$finalType = [ | ||
'name' => $lkup, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think that is an anti pattern to leak class name into the JSON representation.... it might be a "quick thing" but it has backfired at me so many times...
It can represent also potential security issues as it allows third parties to instantiate potentially any class.
are you using this approach? (as example in the "inheritance" discriminator map this feature is intentionally requiring the discriminator map to be not empty)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the alternative to putting the className into the type.
This can happen in many ways. The simplest I can think of is:
class SomeClass {
private MappedDiscriminatedAuthor $data
}
will result in such a type:
$type = ['name' => 'MappedDiscriminatedAuthor', 'params' => []]
I think I must be misunderstanding your question.
@@ -102,6 +102,10 @@ public function loadMetadataForClass(ReflectionClass $class): ?ClassMetadata | |||
foreach ($classMetadata->propertyMetadata as $propertyMetadata) { | |||
// If the inner driver provides a type, don't guess anymore. | |||
if ($propertyMetadata->type) { | |||
if ('union' === $propertyMetadata->type['name']) { | |||
$propertyMetadata->setType($this->reorderTypes($propertyMetadata->type)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could potentially encode in the union
type already the info regarding the discriminator field and map.
advantage of it is that it would be done only once at compile cache metadata time vs at runtime as done now.
it should be much faster.
#[UnionDiscriminator(
field: 'objectType',
map: [
'author' => 'JMS\Serializer\Tests\Fixtures\MappedDiscriminatedAuthor',
'comment' => 'JMS\Serializer\Tests\Fixtures\MappedDiscriminatedComment'
]
)]
can be translated to
[
'type' => 'union',
'params' => [
'objectType',
[
'author' => 'JMS\Serializer\Tests\Fixtures\MappedDiscriminatedAuthor',
'comment' => 'JMS\Serializer\Tests\Fixtures\MappedDiscriminatedComment'
]
]
]
in this way you have all the data ready in the UnionHandler
and no need to fetch metadata each time
do you see it as doable approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what you're asking for, so I put together a commit (34439ca) that contains your ask as I understand it.
Please see the most recent commit for the change in full. Basically, my change does the following.
If a UnionDiscriminator annotation exists, then the params
array on the type will be overwritten:
from
[
'name' => 'union',
'params' => [['name' => 'Path\Type1', 'params' => []], ['name' => 'Path\Type2', 'params' => []]
]
to
[
'name' => 'union',
'params' => ['type']
]
If the UnionDiscriminator
annotation contains a field and a map, both are included:
[
'name' => 'union',
'params' => ['type', ['type1' => 'Path\Type1', ...]]
]
The UnionHandler
can handle a params list that contains any of these combinations:
- A param list of possibleTypes (if they are primitives, they will resolve if the data matches).
- A param list with just a field name - the parameter with a matching name will be used to find the resolution type.
- A param list with a field name and a lookup map. The parameter with a matching name will be used to lookup the final type from the lookup map.
Your latest version is exactly what I had in mind, nice! The only thing left in my opinion is to address the potential security issue. With your implementation, class ComplexDiscriminatedUnion
{
#[UnionDiscriminator(field: 'type')]
private DiscriminatedAuthor|DiscriminatedComment $data; If a user sends this JSON {
"type": "DOMDocument"
} with the current implementation the serializer will attempt to instantiate a In this case The solution here is to have the |
@goetas thanks for the feedback. I think I captured all the changes you asked for! |
src/Handler/UnionHandler.php
Outdated
} else { | ||
throw new NonVisitableTypeException('Union Discriminator Map does not contain key \'' . $lkup . '\''); | ||
} | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this else
branch still exposes the ability to pass any class name in the json and later that class will be instantiated. I think that this should be removed
…alizer into idbentley-union-types-discriminated
- use early returns - simplify the reordering of union types
@idbentley I took the liberty of pushing the changes my self, could you please have a look? |
@goetas Your changes look good to me! |
I'm going to be OOTO next week. Feel free to proceed without me! |
@@ -177,6 +177,16 @@ public function accept($data, ?array $type = null) | |||
|
|||
throw new RuntimeException($msg); | |||
|
|||
case 'union': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought - as the logic in growing and most probably is becoming a first class citizen - IMHO using handler might be redundant here. I guess we might go with direct implementation in GraphNavigator in the future. If we need additional logic here, having handler seems to be redundant.
/** | ||
* @var string|null | ||
*/ | ||
public $unionDiscriminatorField; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we moved the code to the type, those fields seems to be not needed anymore. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, can you please clarify? im not sure I understand
Hi @goetas here's a simpler version of PR #1549 - this one only adds support for discriminated complex unions.