diff --git a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php index 6a5413f69d317..f4a234981e6fb 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ObjectNormalizer.php @@ -194,7 +194,11 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; if ($context['_read_attributes'] ?? true) { - return $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute); + return (\is_object($classOrObject) && $this->propertyAccessor->isReadable($classOrObject, $attribute)) || $this->propertyInfoExtractor->isReadable($class, $attribute) || $this->hasAttributeAccessorMethod($class, $attribute); + } + + if (str_contains($attribute, '.')) { + return true; } if ($this->propertyInfoExtractor->isWritable($class, $attribute)) { diff --git a/src/Symfony/Component/Serializer/Tests/Fixtures/property-path-mapping.yaml b/src/Symfony/Component/Serializer/Tests/Fixtures/property-path-mapping.yaml new file mode 100644 index 0000000000000..834b39150fe89 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Fixtures/property-path-mapping.yaml @@ -0,0 +1,5 @@ +Symfony\Component\Serializer\Tests\Normalizer\ObjectOuter: + attributes: + inner.foo: + serialized_name: inner_foo + groups: [ 'read' ] diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 5f88844974cd9..4ff8c114db058 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -25,6 +25,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\Mapping\Loader\YamlFileLoader; use Symfony\Component\Serializer\NameConverter\AdvancedNameConverterInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; @@ -911,6 +912,40 @@ public function testDenormalizeWithIgnoreAnnotationAndPrivateProperties() $this->assertEquals($expected, $obj); } + + public function testNormalizeWithPropertyPath() + { + $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader(__DIR__.'/../Fixtures/property-path-mapping.yaml')); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $dummyInner = new ObjectInner(); + $dummyInner->foo = 'foo'; + $dummy = new ObjectOuter(); + $dummy->setInner($dummyInner); + + $this->assertSame(['inner_foo' => 'foo'], $normalizer->normalize($dummy, 'json', ['groups' => 'read'])); + } + + public function testDenormalizeWithPropertyPath() + { + $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader(__DIR__.'/../Fixtures/property-path-mapping.yaml')); + $normalizer = new ObjectNormalizer($classMetadataFactory, new MetadataAwareNameConverter($classMetadataFactory)); + + $dummy = new ObjectOuter(); + $dummy->setInner(new ObjectInner()); + + $obj = $normalizer->denormalize(['inner_foo' => 'foo'], ObjectOuter::class, 'json', [ + 'object_to_populate' => $dummy, + 'groups' => 'read', + ]); + + $expectedInner = new ObjectInner(); + $expectedInner->foo = 'foo'; + $expected = new ObjectOuter(); + $expected->setInner($expectedInner); + + $this->assertEquals($expected, $obj); + } } class ProxyObjectDummy extends ObjectDummy