Skip to content

Commit

Permalink
bug #21165 [Serializer] int is valid when float is expected when dese…
Browse files Browse the repository at this point in the history
…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 c36f25f
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 5 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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.