[Serializer] fix denormalization of basic property-types in XML and CSV
This commit is contained in:
parent
78eca9607a
commit
3824dafffb
@ -15,7 +15,9 @@ use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
|
|||||||
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
|
||||||
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
|
||||||
use Symfony\Component\PropertyInfo\Type;
|
use Symfony\Component\PropertyInfo\Type;
|
||||||
|
use Symfony\Component\Serializer\Encoder\CsvEncoder;
|
||||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||||
|
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||||
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
|
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
|
||||||
use Symfony\Component\Serializer\Exception\LogicException;
|
use Symfony\Component\Serializer\Exception\LogicException;
|
||||||
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
|
||||||
@ -379,6 +381,61 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
|
|||||||
$data = [$data];
|
$data = [$data];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
|
||||||
|
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
|
||||||
|
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
|
||||||
|
//
|
||||||
|
// This is special to xml and csv format
|
||||||
|
if (
|
||||||
|
\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
'' === $data && $type->isNullable() && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($type->getBuiltinType()) {
|
||||||
|
case Type::BUILTIN_TYPE_BOOL:
|
||||||
|
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
|
||||||
|
if ('false' === $data || '0' === $data) {
|
||||||
|
$data = false;
|
||||||
|
} elseif ('true' === $data || '1' === $data) {
|
||||||
|
$data = true;
|
||||||
|
} else {
|
||||||
|
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Type::BUILTIN_TYPE_INT:
|
||||||
|
if (
|
||||||
|
ctype_digit($data) ||
|
||||||
|
'-' === $data[0] && ctype_digit(substr($data, 1))
|
||||||
|
) {
|
||||||
|
$data = (int) $data;
|
||||||
|
} else {
|
||||||
|
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Type::BUILTIN_TYPE_FLOAT:
|
||||||
|
if (is_numeric($data)) {
|
||||||
|
return (float) $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($data) {
|
||||||
|
case 'NaN':
|
||||||
|
return NAN;
|
||||||
|
case 'INF':
|
||||||
|
return INF;
|
||||||
|
case '-INF':
|
||||||
|
return -INF;
|
||||||
|
default:
|
||||||
|
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
|
if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
|
||||||
$builtinType = Type::BUILTIN_TYPE_OBJECT;
|
$builtinType = Type::BUILTIN_TYPE_OBJECT;
|
||||||
$class = $collectionValueType->getClassName().'[]';
|
$class = $collectionValueType->getClassName().'[]';
|
||||||
|
@ -273,6 +273,79 @@ class AbstractObjectNormalizerTest extends TestCase
|
|||||||
$this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData);
|
$this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDenormalizeBasicTypePropertiesFromXml()
|
||||||
|
{
|
||||||
|
$denormalizer = $this->getDenormalizerForObjectWithBasicProperties();
|
||||||
|
|
||||||
|
// bool
|
||||||
|
$objectWithBooleanProperties = $denormalizer->denormalize(
|
||||||
|
[
|
||||||
|
'boolTrue1' => 'true',
|
||||||
|
'boolFalse1' => 'false',
|
||||||
|
'boolTrue2' => '1',
|
||||||
|
'boolFalse2' => '0',
|
||||||
|
'int1' => '4711',
|
||||||
|
'int2' => '-4711',
|
||||||
|
'float1' => '123.456',
|
||||||
|
'float2' => '-1.2344e56',
|
||||||
|
'float3' => '45E-6',
|
||||||
|
'floatNaN' => 'NaN',
|
||||||
|
'floatInf' => 'INF',
|
||||||
|
'floatNegInf' => '-INF',
|
||||||
|
],
|
||||||
|
ObjectWithBasicProperties::class,
|
||||||
|
'xml'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBooleanProperties);
|
||||||
|
|
||||||
|
// Bool Properties
|
||||||
|
$this->assertTrue($objectWithBooleanProperties->boolTrue1);
|
||||||
|
$this->assertFalse($objectWithBooleanProperties->boolFalse1);
|
||||||
|
$this->assertTrue($objectWithBooleanProperties->boolTrue2);
|
||||||
|
$this->assertFalse($objectWithBooleanProperties->boolFalse2);
|
||||||
|
|
||||||
|
// Integer Properties
|
||||||
|
$this->assertEquals(4711, $objectWithBooleanProperties->int1);
|
||||||
|
$this->assertEquals(-4711, $objectWithBooleanProperties->int2);
|
||||||
|
|
||||||
|
// Float Properties
|
||||||
|
$this->assertEqualsWithDelta(123.456, $objectWithBooleanProperties->float1, 0.01);
|
||||||
|
$this->assertEqualsWithDelta(-1.2344e56, $objectWithBooleanProperties->float2, 1);
|
||||||
|
$this->assertEqualsWithDelta(45E-6, $objectWithBooleanProperties->float3, 1);
|
||||||
|
$this->assertNan($objectWithBooleanProperties->floatNaN);
|
||||||
|
$this->assertInfinite($objectWithBooleanProperties->floatInf);
|
||||||
|
$this->assertEquals(-INF, $objectWithBooleanProperties->floatNegInf);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDenormalizerForObjectWithBasicProperties()
|
||||||
|
{
|
||||||
|
$extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock();
|
||||||
|
$extractor->method('getTypes')
|
||||||
|
->will($this->onConsecutiveCalls(
|
||||||
|
[new Type('bool')],
|
||||||
|
[new Type('bool')],
|
||||||
|
[new Type('bool')],
|
||||||
|
[new Type('bool')],
|
||||||
|
[new Type('int')],
|
||||||
|
[new Type('int')],
|
||||||
|
[new Type('float')],
|
||||||
|
[new Type('float')],
|
||||||
|
[new Type('float')],
|
||||||
|
[new Type('float')],
|
||||||
|
[new Type('float')],
|
||||||
|
[new Type('float')]
|
||||||
|
));
|
||||||
|
|
||||||
|
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
|
||||||
|
$arrayDenormalizer = new ArrayDenormalizerDummy();
|
||||||
|
$serializer = new SerializerCollectionDummy([$arrayDenormalizer, $denormalizer]);
|
||||||
|
$arrayDenormalizer->setSerializer($serializer);
|
||||||
|
$denormalizer->setSerializer($serializer);
|
||||||
|
|
||||||
|
return $denormalizer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that additional attributes throw an exception if no metadata factory is specified.
|
* Test that additional attributes throw an exception if no metadata factory is specified.
|
||||||
*/
|
*/
|
||||||
@ -359,6 +432,45 @@ class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ObjectWithBasicProperties
|
||||||
|
{
|
||||||
|
/** @var bool */
|
||||||
|
public $boolTrue1;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $boolFalse1;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $boolTrue2;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
public $boolFalse2;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
public $int1;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
public $int2;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $float1;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $float2;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $float3;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $floatNaN;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $floatInf;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
public $floatNegInf;
|
||||||
|
}
|
||||||
|
|
||||||
class StringCollection
|
class StringCollection
|
||||||
{
|
{
|
||||||
/** @var string[] */
|
/** @var string[] */
|
||||||
|
Reference in New Issue
Block a user