Skip to content

Commit 1a60616

Browse files
bug symfony#61746 [Serializer] Fix unknown type in denormalization errors when union type used in constructor (d-mitrofanov-v)
This PR was merged into the 6.4 branch. Discussion ---------- [Serializer] Fix unknown type in denormalization errors when union type used in constructor | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | yes | New feature? | no <!-- if yes, also update src/**/CHANGELOG.md --> | Deprecations? | no <!-- if yes, also update UPGRADE-*.md and src/**/CHANGELOG.md --> | Issues | Fix - <!-- prefix each issue number with "Fix #"; no need to create an issue if none exists, explain below --> | License | MIT Fixes getting "unknown" in `expectedTypes` of `NotNormalizableValueException` if constructor properties have union types and if this property is absent from json. This also fixes getting error "This value should be of type unknown" when building text for `PartialDenormalizationException` in `RequestPayloadValueResolver`. The resulting string will be "This value should be of type int|float". The only problem that i see is that `ReflectionUnionType::getTypes()` returns types in its own order which might not be the same as the order in constructor. For example, even if we have `float|int` in constructor, `ReflectionUnionType::getTypes()` will return types in `int, float` order. But it seems that getting types in a different order is still better then getting "unknown" Commits ------- eb8f941 [Serializer] Fix unknown type in denormalization errors when union type used in constructor
2 parents 2924491 + eb8f941 commit 1a60616

File tree

3 files changed

+89
-4
lines changed

3 files changed

+89
-4
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,16 +408,22 @@ protected function instantiateObject(array &$data, string $class, array &$contex
408408
continue;
409409
}
410410

411-
$constructorParameterType = 'unknown';
411+
$constructorParameterTypes = [];
412412
$reflectionType = $constructorParameter->getType();
413-
if ($reflectionType instanceof \ReflectionNamedType) {
414-
$constructorParameterType = $reflectionType->getName();
413+
if ($reflectionType instanceof \ReflectionUnionType) {
414+
foreach ($reflectionType->getTypes() as $reflectionType) {
415+
$constructorParameterTypes[] = (string) $reflectionType;
416+
}
417+
} elseif ($reflectionType instanceof \ReflectionType) {
418+
$constructorParameterTypes[] = (string) $reflectionType;
419+
} else {
420+
$constructorParameterTypes[] = 'unknown';
415421
}
416422

417423
$exception = NotNormalizableValueException::createForUnexpectedDataType(
418424
\sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name),
419425
null,
420-
[$constructorParameterType],
426+
$constructorParameterTypes,
421427
$attributeContext['deserialization_path'] ?? null,
422428
true
423429
);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Fixtures;
13+
14+
/**
15+
* @author Dmitrii <github.com/d-mitrofanov-v>
16+
*/
17+
class DummyWithUnion
18+
{
19+
public function __construct(
20+
public int|float $value,
21+
public string|int $value2,
22+
) {
23+
}
24+
}

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumConstructor;
6363
use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty;
6464
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull;
65+
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithUnion;
6566
use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicParameter;
6667
use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy;
6768
use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy;
@@ -1392,6 +1393,60 @@ public function testCollectDenormalizationErrorsWithInvalidConstructorTypes()
13921393
$this->assertSame($expected, $exceptionsAsArray);
13931394
}
13941395

1396+
public function testCollectDenormalizationErrorsWithUnionConstructorTypes()
1397+
{
1398+
$json = '{}';
1399+
1400+
$serializer = new Serializer(
1401+
[new ObjectNormalizer()],
1402+
['json' => new JsonEncoder()]
1403+
);
1404+
1405+
try {
1406+
$serializer->deserialize(
1407+
$json,
1408+
DummyWithUnion::class,
1409+
'json',
1410+
[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true]
1411+
);
1412+
1413+
$this->fail();
1414+
} catch (\Throwable $th) {
1415+
$this->assertInstanceOf(PartialDenormalizationException::class, $th);
1416+
}
1417+
1418+
$exceptionsAsArray = array_map(fn (NotNormalizableValueException $e): array => [
1419+
'currentType' => $e->getCurrentType(),
1420+
'expectedTypes' => $e->getExpectedTypes(),
1421+
'path' => $e->getPath(),
1422+
'useMessageForUser' => $e->canUseMessageForUser(),
1423+
'message' => $e->getMessage(),
1424+
], $th->getErrors());
1425+
1426+
$expected = [
1427+
[
1428+
'currentType' => 'null',
1429+
'expectedTypes' => [
1430+
'int', 'float',
1431+
],
1432+
'path' => 'value',
1433+
'useMessageForUser' => true,
1434+
'message' => 'Failed to create object because the class misses the "value" property.',
1435+
],
1436+
[
1437+
'currentType' => 'null',
1438+
'expectedTypes' => [
1439+
'string', 'int',
1440+
],
1441+
'path' => 'value2',
1442+
'useMessageForUser' => true,
1443+
'message' => 'Failed to create object because the class misses the "value2" property.',
1444+
],
1445+
];
1446+
1447+
$this->assertSame($expected, $exceptionsAsArray);
1448+
}
1449+
13951450
public function testCollectDenormalizationErrorsWithEnumConstructor()
13961451
{
13971452
$serializer = new Serializer(

0 commit comments

Comments
 (0)