Skip to content

Commit 70b76e9

Browse files
author
Marcin Czarnecki
committed
Do not change types during deseralisation.
1 parent b4285f4 commit 70b76e9

File tree

5 files changed

+112
-29
lines changed

5 files changed

+112
-29
lines changed

src/Handler/UnionHandler.php

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
namespace JMS\Serializer\Handler;
66

7-
use JMS\Serializer\Context;
87
use JMS\Serializer\DeserializationContext;
98
use JMS\Serializer\Exception\NonVisitableTypeException;
9+
use JMS\Serializer\Exception\NotAcceptableException;
1010
use JMS\Serializer\Exception\RuntimeException;
1111
use JMS\Serializer\GraphNavigatorInterface;
1212
use JMS\Serializer\SerializationContext;
@@ -50,15 +50,18 @@ public function serializeUnion(
5050
SerializationContext $context
5151
): mixed {
5252
if ($this->isPrimitiveType(gettype($data))) {
53-
return $this->matchSimpleType($data, $type, $context);
53+
$resolvedType = [
54+
'name' => gettype($data),
55+
'params' => [],
56+
];
5457
} else {
5558
$resolvedType = [
5659
'name' => get_class($data),
5760
'params' => [],
5861
];
59-
60-
return $context->getNavigator()->accept($data, $resolvedType);
6162
}
63+
64+
return $context->getNavigator()->accept($data, $resolvedType);
6265
}
6366

6467
public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed $data, array $type, DeserializationContext $context): mixed
@@ -87,38 +90,35 @@ public function deserializeUnion(DeserializationVisitorInterface $visitor, mixed
8790
return $context->getNavigator()->accept($data, $finalType);
8891
}
8992

90-
foreach ($type['params'][0] as $possibleType) {
91-
if ($this->isPrimitiveType($possibleType['name']) && $this->testPrimitive($data, $possibleType['name'], $context->getFormat())) {
92-
return $context->getNavigator()->accept($data, $possibleType);
93-
}
94-
}
93+
$dataType = gettype($data);
9594

96-
return null;
97-
}
95+
if (
96+
array_filter(
97+
$type['params'][0],
98+
static fn (array $type): bool => $type['name'] === $dataType || (isset(self::$aliases[$dataType]) && $type['name'] === self::$aliases[$dataType]),
99+
)
100+
) {
101+
return $context->getNavigator()->accept($data, [
102+
'name' => $dataType,
103+
'params' => [],
104+
]);
105+
}
98106

99-
private function matchSimpleType(mixed $data, array $type, Context $context): mixed
100-
{
101107
foreach ($type['params'][0] as $possibleType) {
102-
if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) {
103-
continue;
104-
}
105-
106-
try {
108+
if ($this->isPrimitiveType($possibleType['name']) && $this->testPrimitive($data, $possibleType['name'])) {
107109
return $context->getNavigator()->accept($data, $possibleType);
108-
} catch (NonVisitableTypeException $e) {
109-
continue;
110110
}
111111
}
112112

113-
return null;
113+
throw new NotAcceptableException();
114114
}
115115

116116
private function isPrimitiveType(string $type): bool
117117
{
118118
return in_array($type, ['int', 'integer', 'float', 'double', 'bool', 'boolean', 'string', 'array'], true);
119119
}
120120

121-
private function testPrimitive(mixed $data, string $type, string $format): bool
121+
private function testPrimitive(mixed $data, string $type): bool
122122
{
123123
switch ($type) {
124124
case 'array':
@@ -137,7 +137,7 @@ private function testPrimitive(mixed $data, string $type, string $format): bool
137137
return (string) (bool) $data === (string) $data;
138138

139139
case 'string':
140-
return is_string($data);
140+
return !is_array($data) && !is_object($data);
141141
}
142142

143143
return false;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace JMS\Serializer\Tests\Fixtures\TypedProperties;
6+
7+
class BoolOrString
8+
{
9+
public bool|string $data;
10+
11+
public function __construct($data)
12+
{
13+
$this->data = $data;
14+
}
15+
}

tests/Fixtures/TypedProperties/UnionTypedProperties.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
class UnionTypedProperties
88
{
9-
private int|bool|float|string|array $data;
9+
public bool|float|string|array|int $data;
1010

1111
private int|bool|float|string|null $nullableData;
1212

tests/Serializer/JsonSerializationTest.php

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use JMS\Serializer\Tests\Fixtures\ObjectWithInlineArray;
2525
use JMS\Serializer\Tests\Fixtures\ObjectWithObjectProperty;
2626
use JMS\Serializer\Tests\Fixtures\Tag;
27+
use JMS\Serializer\Tests\Fixtures\TypedProperties\BoolOrString;
2728
use JMS\Serializer\Tests\Fixtures\TypedProperties\ComplexDiscriminatedUnion;
2829
use JMS\Serializer\Tests\Fixtures\TypedProperties\UnionTypedProperties;
2930
use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory;
@@ -151,9 +152,12 @@ protected static function getContent($key)
151152
$outputs['uninitialized_typed_props'] = '{"virtual_role":{},"id":1,"role":{},"tags":[]}';
152153
$outputs['custom_datetimeinterface'] = '{"custom":"2021-09-07"}';
153154
$outputs['data_integer'] = '{"data":10000}';
155+
$outputs['data_integer_one'] = '{"data":1}';
154156
$outputs['data_float'] = '{"data":1.236}';
155157
$outputs['data_bool'] = '{"data":false}';
156158
$outputs['data_string'] = '{"data":"foo"}';
159+
$outputs['data_string_empty'] = '{"data":""}';
160+
$outputs['data_string_zero'] = '{"data":"0"}';
157161
$outputs['data_array'] = '{"data":[1,2,3]}';
158162
$outputs['data_true'] = '{"data":true}';
159163
$outputs['data_false'] = '{"data":false}';
@@ -446,11 +450,16 @@ public static function getTypeHintedArraysAndStdClass()
446450

447451
public static function getSimpleUnionProperties(): iterable
448452
{
449-
yield 'int' => [10000, 'data_integer'];
453+
yield [10000, 'data_integer'];
450454
yield [1.236, 'data_float'];
451455
yield [false, 'data_bool'];
452456
yield ['foo', 'data_string'];
453457
yield [[1, 2, 3], 'data_array'];
458+
yield [1, 'data_integer_one'];
459+
yield ['0', 'data_string_zero'];
460+
yield ['', 'data_string_empty'];
461+
yield [true, 'data_true'];
462+
yield [false, 'data_false'];
454463
}
455464

456465
/**
@@ -465,9 +474,57 @@ public function testUnionProperties($data, string $expected): void
465474
return;
466475
}
467476

468-
$object = new UnionTypedProperties($data);
469-
self::assertEquals($object, $this->deserialize(static::getContent($expected), UnionTypedProperties::class));
470-
self::assertEquals($this->serialize($object), static::getContent($expected));
477+
$deserialized = $this->deserialize(static::getContent($expected), UnionTypedProperties::class);
478+
479+
self::assertSame($data, $deserialized->data);
480+
self::assertSame($this->serialize($deserialized), static::getContent($expected));
481+
}
482+
483+
public static function getUnionCastableTypes(): iterable
484+
{
485+
yield ['10000', 'data_integer'];
486+
yield ['1.236', 'data_float'];
487+
yield [true, 'data_integer_one'];
488+
}
489+
490+
/**
491+
* @dataProvider getUnionCastableTypes
492+
*/
493+
#[DataProvider('getUnionCastableTypes')]
494+
public function testUnionPropertiesWithCastableType($data, string $expected)
495+
{
496+
if (PHP_VERSION_ID < 80000) {
497+
$this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class));
498+
499+
return;
500+
}
501+
502+
$deserialized = $this->deserialize(static::getContent($expected), BoolOrString::class);
503+
504+
self::assertSame($data, $deserialized->data);
505+
}
506+
507+
public static function getUnionNotCastableTypes(): iterable
508+
{
509+
yield ['data_array'];
510+
}
511+
512+
/**
513+
* @dataProvider getUnionNotCastableTypes
514+
*/
515+
#[DataProvider('getUnionNotCastableTypes')]
516+
public function testUnionPropertiesWithNotCastableType(string $expected)
517+
{
518+
if (PHP_VERSION_ID < 80000) {
519+
$this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class));
520+
521+
return;
522+
}
523+
524+
$deserialized = $this->deserialize(static::getContent($expected), BoolOrString::class);
525+
526+
$this->expectException(\Error::class);
527+
$deserialized->data;
471528
}
472529

473530
public function testTrueDataType()
@@ -482,7 +539,6 @@ public function testTrueDataType()
482539
static::getContent('data_true'),
483540
$this->serialize(new DataTrue(true)),
484541
);
485-
486542
self::assertEquals(
487543
new DataTrue(true),
488544
$this->deserialize(static::getContent('data_true'), DataTrue::class),

tests/Serializer/JsonStrictSerializationTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace JMS\Serializer\Tests\Serializer;
66

7+
use JMS\Serializer\Exception\NonVisitableTypeException;
78
use JMS\Serializer\SerializerBuilder;
89
use JMS\Serializer\Visitor\Factory\JsonDeserializationVisitorFactory;
910
use PHPUnit\Framework\Attributes\DataProvider;
@@ -25,4 +26,15 @@ public function testFirstClassMapCollections(array $items, string $expected): vo
2526
{
2627
self::markTestIncomplete('Fixtures are broken');
2728
}
29+
30+
/**
31+
* @dataProvider getUnionCastableTypes
32+
*/
33+
#[DataProvider('getUnionCastableTypes')]
34+
public function testUnionPropertiesWithCastableType($data, string $expected): void
35+
{
36+
$this->expectException(NonVisitableTypeException::class);
37+
38+
parent::testUnionPropertiesWithCastableType($data, $expected);
39+
}
2840
}

0 commit comments

Comments
 (0)