Skip to content

Commit

Permalink
Do not change types during deseralisation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcin Czarnecki committed Dec 10, 2024
1 parent b4285f4 commit 70b76e9
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 29 deletions.
46 changes: 23 additions & 23 deletions src/Handler/UnionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace JMS\Serializer\Handler;

use JMS\Serializer\Context;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Exception\NonVisitableTypeException;
use JMS\Serializer\Exception\NotAcceptableException;
use JMS\Serializer\Exception\RuntimeException;
use JMS\Serializer\GraphNavigatorInterface;
use JMS\Serializer\SerializationContext;
Expand Down Expand Up @@ -50,15 +50,18 @@ public function serializeUnion(
SerializationContext $context
): mixed {
if ($this->isPrimitiveType(gettype($data))) {
return $this->matchSimpleType($data, $type, $context);
$resolvedType = [
'name' => gettype($data),
'params' => [],
];
} else {
$resolvedType = [
'name' => get_class($data),
'params' => [],
];

return $context->getNavigator()->accept($data, $resolvedType);
}

return $context->getNavigator()->accept($data, $resolvedType);
}

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

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

return null;
}
if (
array_filter(
$type['params'][0],
static fn (array $type): bool => $type['name'] === $dataType || (isset(self::$aliases[$dataType]) && $type['name'] === self::$aliases[$dataType]),
)
) {
return $context->getNavigator()->accept($data, [
'name' => $dataType,
'params' => [],
]);
}

private function matchSimpleType(mixed $data, array $type, Context $context): mixed
{
foreach ($type['params'][0] as $possibleType) {
if ($this->isPrimitiveType($possibleType['name']) && !$this->testPrimitive($data, $possibleType['name'], $context->getFormat())) {
continue;
}

try {
if ($this->isPrimitiveType($possibleType['name']) && $this->testPrimitive($data, $possibleType['name'])) {
return $context->getNavigator()->accept($data, $possibleType);
} catch (NonVisitableTypeException $e) {
continue;
}
}

return null;
throw new NotAcceptableException();
}

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

private function testPrimitive(mixed $data, string $type, string $format): bool
private function testPrimitive(mixed $data, string $type): bool
{
switch ($type) {
case 'array':
Expand All @@ -137,7 +137,7 @@ private function testPrimitive(mixed $data, string $type, string $format): bool
return (string) (bool) $data === (string) $data;

case 'string':
return is_string($data);
return !is_array($data) && !is_object($data);
}

return false;
Expand Down
15 changes: 15 additions & 0 deletions tests/Fixtures/TypedProperties/BoolOrString.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Tests\Fixtures\TypedProperties;

class BoolOrString
{
public bool|string $data;

public function __construct($data)
{
$this->data = $data;
}
}
2 changes: 1 addition & 1 deletion tests/Fixtures/TypedProperties/UnionTypedProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

class UnionTypedProperties
{
private int|bool|float|string|array $data;
public bool|float|string|array|int $data;

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

Expand Down
66 changes: 61 additions & 5 deletions tests/Serializer/JsonSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use JMS\Serializer\Tests\Fixtures\ObjectWithInlineArray;
use JMS\Serializer\Tests\Fixtures\ObjectWithObjectProperty;
use JMS\Serializer\Tests\Fixtures\Tag;
use JMS\Serializer\Tests\Fixtures\TypedProperties\BoolOrString;
use JMS\Serializer\Tests\Fixtures\TypedProperties\ComplexDiscriminatedUnion;
use JMS\Serializer\Tests\Fixtures\TypedProperties\UnionTypedProperties;
use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory;
Expand Down Expand Up @@ -151,9 +152,12 @@ protected static function getContent($key)
$outputs['uninitialized_typed_props'] = '{"virtual_role":{},"id":1,"role":{},"tags":[]}';
$outputs['custom_datetimeinterface'] = '{"custom":"2021-09-07"}';
$outputs['data_integer'] = '{"data":10000}';
$outputs['data_integer_one'] = '{"data":1}';
$outputs['data_float'] = '{"data":1.236}';
$outputs['data_bool'] = '{"data":false}';
$outputs['data_string'] = '{"data":"foo"}';
$outputs['data_string_empty'] = '{"data":""}';
$outputs['data_string_zero'] = '{"data":"0"}';
$outputs['data_array'] = '{"data":[1,2,3]}';
$outputs['data_true'] = '{"data":true}';
$outputs['data_false'] = '{"data":false}';
Expand Down Expand Up @@ -446,11 +450,16 @@ public static function getTypeHintedArraysAndStdClass()

public static function getSimpleUnionProperties(): iterable
{
yield 'int' => [10000, 'data_integer'];
yield [10000, 'data_integer'];
yield [1.236, 'data_float'];
yield [false, 'data_bool'];
yield ['foo', 'data_string'];
yield [[1, 2, 3], 'data_array'];
yield [1, 'data_integer_one'];
yield ['0', 'data_string_zero'];
yield ['', 'data_string_empty'];
yield [true, 'data_true'];
yield [false, 'data_false'];
}

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

$object = new UnionTypedProperties($data);
self::assertEquals($object, $this->deserialize(static::getContent($expected), UnionTypedProperties::class));
self::assertEquals($this->serialize($object), static::getContent($expected));
$deserialized = $this->deserialize(static::getContent($expected), UnionTypedProperties::class);

self::assertSame($data, $deserialized->data);
self::assertSame($this->serialize($deserialized), static::getContent($expected));
}

public static function getUnionCastableTypes(): iterable
{
yield ['10000', 'data_integer'];
yield ['1.236', 'data_float'];
yield [true, 'data_integer_one'];
}

/**
* @dataProvider getUnionCastableTypes
*/
#[DataProvider('getUnionCastableTypes')]
public function testUnionPropertiesWithCastableType($data, string $expected)
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class));

return;
}

$deserialized = $this->deserialize(static::getContent($expected), BoolOrString::class);

self::assertSame($data, $deserialized->data);
}

public static function getUnionNotCastableTypes(): iterable
{
yield ['data_array'];
}

/**
* @dataProvider getUnionNotCastableTypes
*/
#[DataProvider('getUnionNotCastableTypes')]
public function testUnionPropertiesWithNotCastableType(string $expected)
{
if (PHP_VERSION_ID < 80000) {
$this->markTestSkipped(sprintf('%s requires PHP 8.0', TypedPropertiesDriver::class));

return;
}

$deserialized = $this->deserialize(static::getContent($expected), BoolOrString::class);

$this->expectException(\Error::class);
$deserialized->data;
}

public function testTrueDataType()
Expand All @@ -482,7 +539,6 @@ public function testTrueDataType()
static::getContent('data_true'),
$this->serialize(new DataTrue(true)),
);

self::assertEquals(
new DataTrue(true),
$this->deserialize(static::getContent('data_true'), DataTrue::class),
Expand Down
12 changes: 12 additions & 0 deletions tests/Serializer/JsonStrictSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace JMS\Serializer\Tests\Serializer;

use JMS\Serializer\Exception\NonVisitableTypeException;
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\Visitor\Factory\JsonDeserializationVisitorFactory;
use PHPUnit\Framework\Attributes\DataProvider;
Expand All @@ -25,4 +26,15 @@ public function testFirstClassMapCollections(array $items, string $expected): vo
{
self::markTestIncomplete('Fixtures are broken');
}

/**
* @dataProvider getUnionCastableTypes
*/
#[DataProvider('getUnionCastableTypes')]
public function testUnionPropertiesWithCastableType($data, string $expected): void
{
$this->expectException(NonVisitableTypeException::class);

parent::testUnionPropertiesWithCastableType($data, $expected);
}
}

0 comments on commit 70b76e9

Please sign in to comment.