Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3ff1be8

Browse files
committed
[Serializer] Fix deserializing nested arrays of objects with mixed keys
1 parent 6f2e603 commit 3ff1be8

File tree

4 files changed

+94
-16
lines changed

4 files changed

+94
-16
lines changed

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
532532
$class = $collectionValueType->getClassName().'[]';
533533

534534
if (\count($collectionKeyType = $type->getCollectionKeyTypes()) > 0) {
535-
[$context['key_type']] = $collectionKeyType;
535+
$context['key_type'] = \count($collectionKeyType) > 1 ? $collectionKeyType : $collectionKeyType[0];
536536
}
537537

538538
$context['value_type'] = $collectionValueType;

src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ public function denormalize($data, string $type, string $format = null, array $c
4949

5050
$type = substr($type, 0, -2);
5151

52-
$builtinType = isset($context['key_type']) ? $context['key_type']->getBuiltinType() : null;
52+
$builtinTypes = array_map(static function (Type $keyType) {
53+
return $keyType->getBuiltinType();
54+
}, \is_array($keyType = $context['key_type'] ?? []) ? $keyType : [$keyType]);
55+
5356
foreach ($data as $key => $value) {
5457
$subContext = $context;
5558
$subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]";
5659

57-
if (null !== $builtinType && !('is_'.$builtinType)($key)) {
58-
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);
59-
}
60+
$this->validateKeyType($builtinTypes, $key, $subContext['deserialization_path']);
6061

6162
$data[$key] = $this->denormalizer->denormalize($value, $type, $format, $subContext);
6263
}
@@ -102,4 +103,26 @@ public function hasCacheableSupportsMethod(): bool
102103
{
103104
return $this->denormalizer instanceof CacheableSupportsMethodInterface && $this->denormalizer->hasCacheableSupportsMethod();
104105
}
106+
107+
/**
108+
* @param mixed $key
109+
*/
110+
private function validateKeyType(array $builtinTypes, $key, string $path): void
111+
{
112+
if (!$builtinTypes) {
113+
return;
114+
}
115+
116+
$unexpectedDataType = true;
117+
foreach ($builtinTypes as $builtinType) {
118+
if (('is_'.$builtinType)($key)) {
119+
$unexpectedDataType = false;
120+
break;
121+
}
122+
}
123+
124+
if ($unexpectedDataType) {
125+
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);
126+
}
127+
}
105128
}

src/Symfony/Component/Serializer/Tests/DeserializeNestedArrayOfObjectsTest.php

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ public static function provider()
3535
*/
3636
public function testPropertyPhpDoc($class)
3737
{
38-
// GIVEN
3938
$json = <<<EOF
4039
{
4140
"animals": [
@@ -47,13 +46,62 @@ public function testPropertyPhpDoc($class)
4746
new ObjectNormalizer(null, null, null, new PhpDocExtractor()),
4847
new ArrayDenormalizer(),
4948
], ['json' => new JsonEncoder()]);
50-
// WHEN
51-
/** @var Zoo $zoo */
49+
50+
/** @var Zoo|ZooImmutable $zoo */
5251
$zoo = $serializer->deserialize($json, $class, 'json');
53-
// THEN
52+
5453
self::assertCount(1, $zoo->getAnimals());
5554
self::assertInstanceOf(Animal::class, $zoo->getAnimals()[0]);
5655
}
56+
57+
public function testPropertyPhpDocWithKeyTypes()
58+
{
59+
$json = <<<EOF
60+
{
61+
"animalsInt": [
62+
{"name": "Bug"}
63+
],
64+
"animalsString": {
65+
"animal1": {"name": "Bug"}
66+
},
67+
"animalsUnion": {
68+
"animal2": {"name": "Bug"},
69+
"2": {"name": "Dog"}
70+
},
71+
"animalsGenerics": {
72+
"animal3": {"name": "Bug"},
73+
"3": {"name": "Dog"}
74+
}
75+
}
76+
EOF;
77+
$serializer = new Serializer([
78+
new ObjectNormalizer(null, null, null, new PhpDocExtractor()),
79+
new ArrayDenormalizer(),
80+
], ['json' => new JsonEncoder()]);
81+
82+
/** @var ZooWithKeyTypes $zoo */
83+
$zoo = $serializer->deserialize($json, ZooWithKeyTypes::class, 'json');
84+
85+
self::assertCount(1, $zoo->animalsInt);
86+
self::assertArrayHasKey(0, $zoo->animalsInt);
87+
self::assertInstanceOf(Animal::class, $zoo->animalsInt[0]);
88+
89+
self::assertCount(1, $zoo->animalsString);
90+
self::assertArrayHasKey('animal1', $zoo->animalsString);
91+
self::assertInstanceOf(Animal::class, $zoo->animalsString['animal1']);
92+
93+
self::assertCount(2, $zoo->animalsUnion);
94+
self::assertArrayHasKey('animal2', $zoo->animalsUnion);
95+
self::assertInstanceOf(Animal::class, $zoo->animalsUnion['animal2']);
96+
self::assertArrayHasKey(2, $zoo->animalsUnion);
97+
self::assertInstanceOf(Animal::class, $zoo->animalsUnion[2]);
98+
99+
self::assertCount(2, $zoo->animalsGenerics);
100+
self::assertArrayHasKey('animal3', $zoo->animalsGenerics);
101+
self::assertInstanceOf(Animal::class, $zoo->animalsGenerics['animal3']);
102+
self::assertArrayHasKey(3, $zoo->animalsGenerics);
103+
self::assertInstanceOf(Animal::class, $zoo->animalsGenerics[3]);
104+
}
57105
}
58106

59107
class Zoo
@@ -100,16 +148,23 @@ public function getAnimals(): array
100148
}
101149
}
102150

151+
class ZooWithKeyTypes
152+
{
153+
/** @var array<int, Animal> */
154+
public $animalsInt = [];
155+
/** @var array<string, Animal> */
156+
public $animalsString = [];
157+
/** @var array<int|string, Animal> */
158+
public $animalsUnion = [];
159+
/** @var \stdClass<Animal> */
160+
public $animalsGenerics = [];
161+
}
162+
103163
class Animal
104164
{
105165
/** @var string */
106166
private $name;
107167

108-
public function __construct()
109-
{
110-
echo '';
111-
}
112-
113168
public function getName(): ?string
114169
{
115170
return $this->name;

src/Symfony/Component/Serializer/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"symfony/http-kernel": "^4.4|^5.0|^6.0",
3535
"symfony/mime": "^4.4|^5.0|^6.0",
3636
"symfony/property-access": "^5.4|^6.0",
37-
"symfony/property-info": "^5.3.13|^6.0",
37+
"symfony/property-info": "^5.4.24|^6.0",
3838
"symfony/uid": "^5.3|^6.0",
3939
"symfony/validator": "^4.4|^5.0|^6.0",
4040
"symfony/var-dumper": "^4.4|^5.0|^6.0",
@@ -47,7 +47,7 @@
4747
"phpdocumentor/type-resolver": "<1.4.0",
4848
"symfony/dependency-injection": "<4.4",
4949
"symfony/property-access": "<5.4",
50-
"symfony/property-info": "<5.3.13",
50+
"symfony/property-info": "<5.4.24",
5151
"symfony/uid": "<5.3",
5252
"symfony/yaml": "<4.4"
5353
},

0 commit comments

Comments
 (0)