From 4125455775aed7d495d102f6ddfb6b3d95f9e163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 4 Jan 2017 22:43:03 +0100 Subject: [PATCH] [Serializer] int is valid when float is expected when deserializing JSON --- .../Normalizer/AbstractObjectNormalizer.php | 11 ++++++++ .../Tests/Normalizer/ObjectNormalizerTest.php | 28 +++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index 937154a89b..9edd332481 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Normalizer; use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException; +use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Exception\CircularReferenceException; use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -260,6 +261,16 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer } } + // JSON only has a Number type corresponding to both int and float PHP types. + // PHP's json_encode, JavaScript's JSON.stringify, Go's json.Marshal as well as most other JSON encoders convert + // floating-point numbers like 12.0 to 12 (the decimal part is dropped when possible). + // PHP's json_decode automatically converts Numbers without a decimal part to integers. + // To circumvent this behavior, integers are converted to floats when denormalizing JSON based formats and when + // a float is expected. + if (Type::BUILTIN_TYPE_FLOAT === $builtinType && is_int($data) && false !== strpos($format, JsonEncoder::FORMAT)) { + return (float) $data; + } + if (call_user_func('is_'.$builtinType, $data)) { return $data; } diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php index 30a936d95c..72f035462c 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php @@ -537,11 +537,21 @@ class ObjectNormalizerTest extends \PHPUnit_Framework_TestCase 'inners' => array(array('foo' => 1), array('foo' => 2)), ), ObjectOuter::class); - $this->assertEquals('foo', $obj->getInner()->foo); - $this->assertEquals('bar', $obj->getInner()->bar); - $this->assertEquals('1988-01-21', $obj->getDate()->format('Y-m-d')); - $this->assertEquals(1, $obj->getInners()[0]->foo); - $this->assertEquals(2, $obj->getInners()[1]->foo); + $this->assertSame('foo', $obj->getInner()->foo); + $this->assertSame('bar', $obj->getInner()->bar); + $this->assertSame('1988-01-21', $obj->getDate()->format('Y-m-d')); + $this->assertSame(1, $obj->getInners()[0]->foo); + $this->assertSame(2, $obj->getInners()[1]->foo); + } + + public function testAcceptJsonNumber() + { + $extractor = new PropertyInfoExtractor(array(), array(new PhpDocExtractor(), new ReflectionExtractor())); + $normalizer = new ObjectNormalizer(null, null, null, $extractor); + $serializer = new Serializer(array(new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer)); + + $this->assertSame(10.0, $serializer->denormalize(array('number' => 10), JsonNumber::class, 'json')->number); + $this->assertSame(10.0, $serializer->denormalize(array('number' => 10), JsonNumber::class, 'jsonld')->number); } /** @@ -820,3 +830,11 @@ class FormatAndContextAwareNormalizer extends ObjectNormalizer return false; } } + +class JsonNumber +{ + /** + * @var float + */ + public $number; +}