diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 7f8d341846..08e5841060 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -294,7 +294,7 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer */ private function validateAndDenormalize(string $currentClass, string $attribute, $data, ?string $format, array $context) { - if (null === $this->propertyTypeExtractor || null === $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) { + if (null === $types = $this->getTypes($currentClass, $attribute)) { return $data; } @@ -357,6 +357,36 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), gettype($data))); } + /** + * @return Type[]|null + */ + private function getTypes(string $currentClass, string $attribute) + { + if (null === $this->propertyTypeExtractor) { + return null; + } + + if (null !== $types = $this->propertyTypeExtractor->getTypes($currentClass, $attribute)) { + return $types; + } + + if (null !== $this->classDiscriminatorResolver && null !== $discriminatorMapping = $this->classDiscriminatorResolver->getMappingForClass($currentClass)) { + if ($discriminatorMapping->getTypeProperty() === $attribute) { + return array( + new Type(Type::BUILTIN_TYPE_STRING), + ); + } + + foreach ($discriminatorMapping->getTypesMapping() as $mappedClass) { + if (null !== $types = $this->propertyTypeExtractor->getTypes($mappedClass, $attribute)) { + return $types; + } + } + } + + return null; + } + /** * Sets an attribute and apply the name converter if necessary. * diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php index 3b828e50bf..5a24e7c9ff 100644 --- a/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/DummyMessageNumberTwo.php @@ -22,4 +22,19 @@ class DummyMessageNumberTwo implements DummyMessageInterface * @Groups({"two"}) */ public $three; + + /** + * @var DummyMessageNumberOne + */ + private $nested; + + public function setNested(DummyMessageNumberOne $nested) + { + $this->nested = $nested; + } + + public function getNested(): DummyMessageNumberOne + { + return $this->nested; + } } diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php index 25fbbf38ce..743461a727 100644 --- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php +++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Serializer\Tests; use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata; use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping; use Symfony\Component\Serializer\Mapping\ClassMetadata; @@ -35,6 +36,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummyFirstChild; use Symfony\Component\Serializer\Tests\Fixtures\AbstractDummySecondChild; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface; use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberOne; +use Symfony\Component\Serializer\Tests\Fixtures\DummyMessageNumberTwo; use Symfony\Component\Serializer\Tests\Fixtures\TraversableDummy; use Symfony\Component\Serializer\Tests\Fixtures\NormalizableTraversableDummy; use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer; @@ -430,6 +432,22 @@ class SerializerTest extends TestCase $this->assertEquals('{"two":2,"type":"one"}', $serialized); } + public function testDeserializeAndSerializeNestedInterfacedObjectsWithTheClassMetadataDiscriminator() + { + $nested = new DummyMessageNumberOne(); + $nested->one = 'foo'; + + $example = new DummyMessageNumberTwo(); + $example->setNested($nested); + + $serializer = $this->serializerWithClassDiscriminator(); + + $serialized = $serializer->serialize($example, 'json'); + $deserialized = $serializer->deserialize($serialized, DummyMessageInterface::class, 'json'); + + $this->assertEquals($example, $deserialized); + } + /** * @expectedException \Symfony\Component\Serializer\Exception\RuntimeException * @expectedExceptionMessage The type "second" has no mapped class for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\DummyMessageInterface" @@ -452,7 +470,7 @@ class SerializerTest extends TestCase { $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); - return new Serializer(array(new ObjectNormalizer($classMetadataFactory, null, null, null, new ClassDiscriminatorFromClassMetadata($classMetadataFactory))), array('json' => new JsonEncoder())); + return new Serializer(array(new ObjectNormalizer($classMetadataFactory, null, null, new ReflectionExtractor(), new ClassDiscriminatorFromClassMetadata($classMetadataFactory))), array('json' => new JsonEncoder())); } }