Skip to content
Permalink
Browse files

bug #21165 [Serializer] int is valid when float is expected when dese…

…rializing JSON (dunglas)

This PR was squashed before being merged into the 3.1 branch (closes #21165).

Discussion
----------

[Serializer] int is valid when float is expected when deserializing JSON

| Q             | A
| ------------- | ---
| Branch?       | 3.1
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | api-platform/api-platform#37
| License       | MIT
| Doc PR        | n/a

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.

Actually, the Serializer rejects integers when a float is expected, this PR fixes this behavior when denormalizing JSON-based formats.

Port of api-platform/core#714.

/cc @gorghoa @Shine-neko

Commits
-------

4125455 [Serializer] int is valid when float is expected when deserializing JSON
  • Loading branch information...
fabpot committed Jan 6, 2017
2 parents d7928ea + 4125455 commit c36f25f03854a4e2eb31f77de9f4f00d57851202
@@ -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 @@ private function validateAndDenormalize($currentClass, $attribute, $data, $forma
}
}
// 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;
}
@@ -537,11 +537,21 @@ public function testDenomalizeRecursive()
'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 @@ protected function isAllowedAttribute($classOrObject, $attribute, $format = null
return false;
}
}
class JsonNumber
{
/**
* @var float
*/
public $number;
}

0 comments on commit c36f25f

Please sign in to comment.
You can’t perform that action at this time.