From 1c2e0d33ed783109a8018df42a6fe6a4708d46ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radoslav=20Gren=C4=8D=C3=ADk?= Date: Mon, 10 Aug 2026 10:10:27 +0200 Subject: [PATCH] fix(mapper): preserve native scalar values --- docs/2-features/01-mapper.md | 2 +- packages/mapper/src/Serializer.php | 4 ++-- .../src/Serializers/BooleanSerializer.php | 4 ++-- .../src/Serializers/FloatSerializer.php | 4 ++-- .../src/Serializers/IntegerSerializer.php | 4 ++-- .../Fixtures/ObjectWithScalarValues.php | 17 ++++++++++++++ .../Mappers/ObjectToArrayMapperTest.php | 22 ++++++++++++++++++- .../Mapper/Mappers/ObjectToJsonMapperTest.php | 20 +++++++++++++++++ .../ArrayOfObjectsSerializerTest.php | 6 ++--- .../Serializers/BooleanSerializerTest.php | 4 ++-- .../Serializers/FloatSerializerTest.php | 2 +- .../Serializers/IntegerSerializerTest.php | 2 +- 12 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 tests/Integration/Mapper/Fixtures/ObjectWithScalarValues.php diff --git a/docs/2-features/01-mapper.md b/docs/2-features/01-mapper.md index 4dcad42d9e..933ca3238b 100644 --- a/docs/2-features/01-mapper.md +++ b/docs/2-features/01-mapper.md @@ -242,7 +242,7 @@ final readonly class AddressSerializer implements Serializer ::: -Of course, Tempest provides casters and serializers for the most common data types, including arrays, booleans, dates, enumerations, integers and value objects. +Of course, Tempest provides casters and serializers for the most common data types, including arrays, booleans, dates, enumerations, floats, integers and value objects. ### Registering casters and serializers globally diff --git a/packages/mapper/src/Serializer.php b/packages/mapper/src/Serializer.php index 1e18af8dd8..8d03a63c7f 100644 --- a/packages/mapper/src/Serializer.php +++ b/packages/mapper/src/Serializer.php @@ -7,7 +7,7 @@ interface Serializer { /** - * Serializes the given input into a string, array, or integer. + * Serializes the given input into an array, boolean, float, integer, or string. */ - public function serialize(mixed $input): array|string|int; + public function serialize(mixed $input): array|bool|float|int|string; } diff --git a/packages/mapper/src/Serializers/BooleanSerializer.php b/packages/mapper/src/Serializers/BooleanSerializer.php index 06611cc160..d892ce0bd3 100644 --- a/packages/mapper/src/Serializers/BooleanSerializer.php +++ b/packages/mapper/src/Serializers/BooleanSerializer.php @@ -23,12 +23,12 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool return in_array($type->getName(), ['bool', 'boolean'], strict: true); } - public function serialize(mixed $input): string + public function serialize(mixed $input): bool { if (! is_bool($input)) { throw new ValueCouldNotBeSerialized('boolean'); } - return $input ? 'true' : 'false'; + return $input; } } diff --git a/packages/mapper/src/Serializers/FloatSerializer.php b/packages/mapper/src/Serializers/FloatSerializer.php index bb547f90fe..e09665ad4c 100644 --- a/packages/mapper/src/Serializers/FloatSerializer.php +++ b/packages/mapper/src/Serializers/FloatSerializer.php @@ -23,12 +23,12 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool return in_array($type->getName(), ['double', 'float'], strict: true); } - public function serialize(mixed $input): string + public function serialize(mixed $input): float { if (! is_float($input)) { throw new ValueCouldNotBeSerialized('float'); } - return (string) $input; + return $input; } } diff --git a/packages/mapper/src/Serializers/IntegerSerializer.php b/packages/mapper/src/Serializers/IntegerSerializer.php index ecac29641f..e9e75e5518 100644 --- a/packages/mapper/src/Serializers/IntegerSerializer.php +++ b/packages/mapper/src/Serializers/IntegerSerializer.php @@ -23,12 +23,12 @@ public static function accepts(PropertyReflector|TypeReflector $input): bool return in_array($type->getName(), ['int', 'integer'], strict: true); } - public function serialize(mixed $input): string + public function serialize(mixed $input): int { if (! is_int($input)) { throw new ValueCouldNotBeSerialized('integer'); } - return (string) $input; + return $input; } } diff --git a/tests/Integration/Mapper/Fixtures/ObjectWithScalarValues.php b/tests/Integration/Mapper/Fixtures/ObjectWithScalarValues.php new file mode 100644 index 0000000000..2570d2ff4d --- /dev/null +++ b/tests/Integration/Mapper/Fixtures/ObjectWithScalarValues.php @@ -0,0 +1,17 @@ +assertSame( [ 'a' => 'a', - 'b' => '3.1416', + 'b' => 3.1416, 'c' => null, ], $array, ); } + + #[Test] + public function object_with_scalar_values_to_array(): void + { + $array = map(new ObjectWithScalarValues( + active: true, + score: 1.5, + count: 3, + ))->toArray(); + + $this->assertSame( + [ + 'active' => true, + 'score' => 1.5, + 'count' => 3, + ], + $array, + ); + } } diff --git a/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTest.php b/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTest.php index 4af0f13dff..a570c3ca77 100644 --- a/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTest.php +++ b/tests/Integration/Mapper/Mappers/ObjectToJsonMapperTest.php @@ -7,6 +7,7 @@ use PHPUnit\Framework\Attributes\Test; use Tests\Tempest\Integration\FrameworkIntegrationTestCase; use Tests\Tempest\Integration\Mapper\Fixtures\ObjectA; +use Tests\Tempest\Integration\Mapper\Fixtures\ObjectWithScalarValues; use function Tempest\Mapper\map; @@ -22,4 +23,23 @@ public function object_to_json(): void $this->assertSame('{"a":"a","b":"b"}', $json); } + + #[Test] + public function object_with_scalar_values_to_json(): void + { + $json = map(new ObjectWithScalarValues( + active: true, + score: 1.5, + count: 3, + ))->toJson(); + + $this->assertSame( + [ + 'active' => true, + 'score' => 1.5, + 'count' => 3, + ], + json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR), + ); + } } diff --git a/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php b/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php index 7c6f590a3e..87d4933473 100644 --- a/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php +++ b/tests/Integration/Mapper/Serializers/ArrayOfObjectsSerializerTest.php @@ -19,11 +19,11 @@ public function serialize(): void [ 'stringProp' => 'a', 'stringableProp' => 'a', - 'intProp' => '1', + 'intProp' => 1, 'nullableIntProp' => null, - 'floatProp' => '0.1', + 'floatProp' => 0.1, 'nullableFloatProp' => null, - 'boolProp' => 'true', + 'boolProp' => true, 'nullableBoolProp' => null, 'arrayProp' => '["a"]', 'serializeWithProp' => 'aa', diff --git a/tests/Integration/Mapper/Serializers/BooleanSerializerTest.php b/tests/Integration/Mapper/Serializers/BooleanSerializerTest.php index 7b271c5448..79fca8755f 100644 --- a/tests/Integration/Mapper/Serializers/BooleanSerializerTest.php +++ b/tests/Integration/Mapper/Serializers/BooleanSerializerTest.php @@ -13,12 +13,12 @@ final class BooleanSerializerTest extends TestCase public function serialize(): void { $this->assertSame( - 'true', + true, new BooleanSerializer()->serialize(true), ); $this->assertSame( - 'false', + false, new BooleanSerializer()->serialize(false), ); } diff --git a/tests/Integration/Mapper/Serializers/FloatSerializerTest.php b/tests/Integration/Mapper/Serializers/FloatSerializerTest.php index 7ae90b08d4..02f446bae1 100644 --- a/tests/Integration/Mapper/Serializers/FloatSerializerTest.php +++ b/tests/Integration/Mapper/Serializers/FloatSerializerTest.php @@ -13,7 +13,7 @@ final class FloatSerializerTest extends TestCase public function serialize(): void { $this->assertSame( - '0.1', + 0.1, new FloatSerializer()->serialize(0.1), ); } diff --git a/tests/Integration/Mapper/Serializers/IntegerSerializerTest.php b/tests/Integration/Mapper/Serializers/IntegerSerializerTest.php index 593a7c8255..b406cdb250 100644 --- a/tests/Integration/Mapper/Serializers/IntegerSerializerTest.php +++ b/tests/Integration/Mapper/Serializers/IntegerSerializerTest.php @@ -13,7 +13,7 @@ final class IntegerSerializerTest extends TestCase public function serialize(): void { $this->assertSame( - '1', + 1, new IntegerSerializer()->serialize(1), ); }