diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 7c9117152..d2d0a8199 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -532,7 +532,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri $class = $collectionValueType->getClassName().'[]'; if (\count($collectionKeyType = $type->getCollectionKeyTypes()) > 0) { - [$context['key_type']] = $collectionKeyType; + $context['key_type'] = \count($collectionKeyType) > 1 ? $collectionKeyType : $collectionKeyType[0]; } $context['value_type'] = $collectionValueType; diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 55c4233fe..1420523c0 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -49,14 +49,15 @@ public function denormalize($data, string $type, string $format = null, array $c $type = substr($type, 0, -2); - $builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null; + $builtinTypes = array_map(static function (Type $keyType) { + return $keyType->getBuiltinType(); + }, \is_array($keyType = $context['key_type'] ?? []) ? $keyType : [$keyType]); + foreach ($data as $key => $value) { $subContext = $context; $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; - if (null !== $builtinType && !('is_'.$builtinType)($key)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, $builtinType, get_debug_type($key)), $key, [$builtinType], $subContext['deserialization_path'] ?? null, true); - } + $this->validateKeyType($builtinTypes, $key, $subContext['deserialization_path']); $data[$key] = $this->denormalizer->denormalize($value, $type, $format, $subContext); } @@ -102,4 +103,22 @@ public function hasCacheableSupportsMethod(): bool { return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod(); } + + /** + * @param mixed $key + */ + private function validateKeyType(array $builtinTypes, $key, string $path): void + { + if (!$builtinTypes) { + return; + } + + foreach ($builtinTypes as $builtinType) { + if (('is_'.$builtinType)($key)) { + return; + } + } + + throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $builtinTypes), get_debug_type($key)), $key, $builtinTypes, $path, true); + } } diff --git a/Tests/DeserializeNestedArrayOfObjectsTest.php b/Tests/DeserializeNestedArrayOfObjectsTest.php index 0be95e921..8da1b471b 100644 --- a/Tests/DeserializeNestedArrayOfObjectsTest.php +++ b/Tests/DeserializeNestedArrayOfObjectsTest.php @@ -35,7 +35,6 @@ public static function provider() */ public function testPropertyPhpDoc($class) { - // GIVEN $json = << new JsonEncoder()]); - // WHEN - /** @var Zoo $zoo */ + + /** @var Zoo|ZooImmutable $zoo */ $zoo = $serializer->deserialize($json, $class, 'json'); - // THEN + self::assertCount(1, $zoo->getAnimals()); self::assertInstanceOf(Animal::class, $zoo->getAnimals()[0]); } + + public function testPropertyPhpDocWithKeyTypes() + { + $json = << new JsonEncoder()]); + + /** @var ZooWithKeyTypes $zoo */ + $zoo = $serializer->deserialize($json, ZooWithKeyTypes::class, 'json'); + + self::assertCount(1, $zoo->animalsInt); + self::assertArrayHasKey(0, $zoo->animalsInt); + self::assertInstanceOf(Animal::class, $zoo->animalsInt[0]); + + self::assertCount(1, $zoo->animalsString); + self::assertArrayHasKey('animal1', $zoo->animalsString); + self::assertInstanceOf(Animal::class, $zoo->animalsString['animal1']); + + self::assertCount(2, $zoo->animalsUnion); + self::assertArrayHasKey('animal2', $zoo->animalsUnion); + self::assertInstanceOf(Animal::class, $zoo->animalsUnion['animal2']); + self::assertArrayHasKey(2, $zoo->animalsUnion); + self::assertInstanceOf(Animal::class, $zoo->animalsUnion[2]); + + self::assertCount(2, $zoo->animalsGenerics); + self::assertArrayHasKey('animal3', $zoo->animalsGenerics); + self::assertInstanceOf(Animal::class, $zoo->animalsGenerics['animal3']); + self::assertArrayHasKey(3, $zoo->animalsGenerics); + self::assertInstanceOf(Animal::class, $zoo->animalsGenerics[3]); + } } class Zoo @@ -100,16 +148,23 @@ public function getAnimals(): array } } +class ZooWithKeyTypes +{ + /** @var array */ + public $animalsInt = []; + /** @var array */ + public $animalsString = []; + /** @var array */ + public $animalsUnion = []; + /** @var \stdClass */ + public $animalsGenerics = []; +} + class Animal { /** @var string */ private $name; - public function __construct() - { - echo ''; - } - public function getName(): ?string { return $this->name; diff --git a/composer.json b/composer.json index 7b9a3c719..3ec14ae0f 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "symfony/http-kernel": "^4.4|^5.0|^6.0", "symfony/mime": "^4.4|^5.0|^6.0", "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.3.13|^6.0", + "symfony/property-info": "^5.4.24|^6.2.11", "symfony/uid": "^5.3|^6.0", "symfony/validator": "^4.4|^5.0|^6.0", "symfony/var-dumper": "^4.4|^5.0|^6.0", @@ -47,7 +47,7 @@ "phpdocumentor/type-resolver": "<1.4.0", "symfony/dependency-injection": "<4.4", "symfony/property-access": "<5.4", - "symfony/property-info": "<5.3.13", + "symfony/property-info": "<5.4.24|>=6,<6.2.11", "symfony/uid": "<5.3", "symfony/yaml": "<4.4" },