Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit db0e893

Browse filesBrowse files
committed
[Serializer] Fix deserializing nested arrays of objects with mixed keys
1 parent 6f2e603 commit db0e893
Copy full SHA for db0e893

File tree

Expand file treeCollapse file tree

4 files changed

+90
-16
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+90
-16
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
+1-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Normalizer/ArrayDenormalizer.php
+23-4Lines changed: 23 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,22 @@ 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+
foreach ($builtinTypes as $builtinType) {
117+
if (('is_'.$builtinType)($key)) {
118+
return;
119+
}
120+
}
121+
122+
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);
123+
}
105124
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Tests/DeserializeNestedArrayOfObjectsTest.php
+64-9Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/composer.json
+2-2Lines 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.2.11",
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|>=6,<6.2.11",
5151
"symfony/uid": "<5.3",
5252
"symfony/yaml": "<4.4"
5353
},

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.