From 645399e95a411766effd5253f252b3c35b659771 Mon Sep 17 00:00:00 2001 From: Viktor Truhanovich Date: Wed, 16 Aug 2023 02:35:19 +0300 Subject: [PATCH] [Serializer] Fix deserializing of nested snake_case attributes using CamelCaseToSnakeCaseNameConverter --- .../Normalizer/AbstractObjectNormalizer.php | 8 +++--- .../AbstractObjectNormalizerTest.php | 25 ++++++++++++++++++- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index b252d62194d87..5187955dd8528 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -326,13 +326,15 @@ public function denormalize(mixed $data, string $type, string $format = null, ar $mappedClass = $this->getMappedClass($normalizedData, $type, $context); $nestedAttributes = $this->getNestedAttributes($mappedClass); - $nestedData = []; + $nestedData = $originalNestedData = []; $propertyAccessor = PropertyAccess::createPropertyAccessor(); foreach ($nestedAttributes as $property => $serializedPath) { if (null === $value = $propertyAccessor->getValue($normalizedData, $serializedPath)) { continue; } - $nestedData[$property] = $value; + $convertedProperty = $this->nameConverter ? $this->nameConverter->normalize($property, $mappedClass, $format, $context) : $property; + $nestedData[$convertedProperty] = $value; + $originalNestedData[$property] = $value; $normalizedData = $this->removeNestedValue($serializedPath->getElements(), $normalizedData); } @@ -345,7 +347,7 @@ public function denormalize(mixed $data, string $type, string $format = null, ar if ($this->nameConverter) { $notConverted = $attribute; $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); - if (isset($nestedData[$notConverted]) && !isset($nestedData[$attribute])) { + if (isset($nestedData[$notConverted]) && !isset($originalNestedData[$attribute])) { throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath annotation: "%s", the other one is set via the SerializedName annotation: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); } } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php index bed7c33cecc19..5cde702256874 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -33,6 +33,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\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; @@ -140,6 +141,20 @@ public function testDenormalizeWithNestedAttributesWithoutMetadata() $this->assertNull($test->notfoo); } + public function testDenormalizeWithSnakeCaseNestedAttributes() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new AbstractObjectNormalizerDummy($factory, new CamelCaseToSnakeCaseNameConverter()); + $serializer = new Serializer([$normalizer]); + $data = [ + 'one' => [ + 'two_three' => 'fooBar', + ], + ]; + $test = $serializer->denormalize($data, SnakeCaseNestedDummy::class, 'any'); + $this->assertSame('fooBar', $test->fooBar); + } + public function testDenormalizeWithNestedAttributes() { $normalizer = new AbstractObjectNormalizerWithMetadata(); @@ -770,7 +785,7 @@ protected function setAttributeValue(object $object, string $attribute, $value, protected function isAllowedAttribute($classOrObject, string $attribute, string $format = null, array $context = []): bool { - return \in_array($attribute, ['foo', 'baz', 'quux', 'value']); + return \in_array($attribute, ['foo', 'baz', 'quux', 'value', 'fooBar']); } public function instantiateObject(array &$data, string $class, array &$context, \ReflectionClass $reflectionClass, $allowedAttributes, string $format = null): object @@ -861,6 +876,14 @@ public function __construct( } } +class SnakeCaseNestedDummy +{ + /** + * @SerializedPath("[one][two_three]") + */ + public $fooBar; +} + /** * @DiscriminatorMap(typeProperty="type", mapping={ * "first" = FirstNestedDummyWithConstructorAndDiscriminator::class,