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..361f8520d3a22 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,29 @@ public function testDenormalizeWithNestedAttributesWithoutMetadata() $this->assertNull($test->notfoo); } + public function testDenormalizeWithSnakeCaseNestedAttributes() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); + $data = [ + 'one' => [ + 'two_three' => 'fooBar', + ], + ]; + $test = $normalizer->denormalize($data, SnakeCaseNestedDummy::class, 'any'); + $this->assertSame('fooBar', $test->fooBar); + } + + public function testNormalizeWithSnakeCaseNestedAttributes() + { + $factory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); + $normalizer = new ObjectNormalizer($factory, new CamelCaseToSnakeCaseNameConverter()); + $dummy = new SnakeCaseNestedDummy(); + $dummy->fooBar = 'fooBar'; + $test = $normalizer->normalize($dummy, 'any'); + $this->assertSame(['one' => ['two_three' => 'fooBar']], $test); + } + public function testDenormalizeWithNestedAttributes() { $normalizer = new AbstractObjectNormalizerWithMetadata(); @@ -861,6 +885,14 @@ public function __construct( } } +class SnakeCaseNestedDummy +{ + /** + * @SerializedPath("[one][two_three]") + */ + public $fooBar; +} + /** * @DiscriminatorMap(typeProperty="type", mapping={ * "first" = FirstNestedDummyWithConstructorAndDiscriminator::class,