From bd623b9466aa0e4e18fecfeb930c9e0ea0ecd4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy=20=28T-bond=29?= Date: Sun, 27 Mar 2022 01:51:41 +0100 Subject: [PATCH] [Serializer] Try all possible denormalization route with union types when ALLOW_EXTRA_ATTRIBUTES=false --- .../Normalizer/AbstractObjectNormalizer.php | 15 ++++- .../Serializer/Tests/SerializerTest.php | 55 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 5977d994987c..36198fcb5468 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -370,7 +370,7 @@ public function denormalize($data, $type, $format = null, array $context = []) } } - if (!empty($extraAttributes)) { + if ($extraAttributes) { throw new ExtraAttributesException($extraAttributes); } @@ -405,6 +405,7 @@ private function validateAndDenormalize(string $currentClass, string $attribute, $expectedTypes = []; $isUnionType = \count($types) > 1; + $extraAttributesException = null; foreach ($types as $type) { if (null === $data && $type->isNullable()) { return null; @@ -494,9 +495,21 @@ private function validateAndDenormalize(string $currentClass, string $attribute, if (!$isUnionType) { throw $e; } + } catch (ExtraAttributesException $e) { + if (!$isUnionType) { + throw $e; + } + + if (!$extraAttributesException) { + $extraAttributesException = $e; + } } } + if ($extraAttributesException) { + throw $extraAttributesException; + } + if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { return $data; } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 06eda94f5360..b5a0f19390f0 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Exception\ExtraAttributesException; use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; @@ -31,6 +32,7 @@ use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\CustomNormalizer; @@ -573,6 +575,35 @@ public function testUnionTypeDeserializable() $this->assertEquals(new DummyUnionType(), $actual, 'Union type denormalization third case failed.'); } + public function testUnionTypeDeserializableWithoutAllowedExtraAttributes() + { + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]); + $serializer = new Serializer( + [ + new ObjectNormalizer($classMetadataFactory, null, null, $extractor, new ClassDiscriminatorFromClassMetadata($classMetadataFactory)), + ], + ['json' => new JsonEncoder()] + ); + + $actual = $serializer->deserialize('{ "v": { "a": 0 }}', DummyUnionWithAAndB::class, 'json', [ + AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, + ]); + + $this->assertEquals(new DummyUnionWithAAndB(new DummyATypeForUnion()), $actual); + + $actual = $serializer->deserialize('{ "v": { "b": 1 }}', DummyUnionWithAAndB::class, 'json', [ + AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, + ]); + + $this->assertEquals(new DummyUnionWithAAndB(new DummyBTypeForUnion()), $actual); + + $this->expectException(ExtraAttributesException::class); + $serializer->deserialize('{ "v": { "b": 1, "c": "i am not allowed" }}', DummyUnionWithAAndB::class, 'json', [ + AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false, + ]); + } + /** * @requires PHP 8.2 */ @@ -678,6 +709,30 @@ public function setChanged($changed): self } } +class DummyATypeForUnion +{ + public $a = 0; +} + +class DummyBTypeForUnion +{ + public $b = 1; +} + +class DummyUnionWithAAndB +{ + /** @var DummyATypeForUnion|DummyBTypeForUnion */ + public $v; + + /** + * @param DummyATypeForUnion|DummyBTypeForUnion $v + */ + public function __construct($v) + { + $this->v = $v; + } +} + interface NormalizerAwareNormalizer extends NormalizerInterface, NormalizerAwareInterface { }