Skip to content

Commit 3824daf

Browse files
mkrauserfabpot
authored andcommitted
[Serializer] fix denormalization of basic property-types in XML and CSV
1 parent 78eca96 commit 3824daf

File tree

2 files changed

+169
-0
lines changed

2 files changed

+169
-0
lines changed

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
1616
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1717
use Symfony\Component\PropertyInfo\Type;
18+
use Symfony\Component\Serializer\Encoder\CsvEncoder;
1819
use Symfony\Component\Serializer\Encoder\JsonEncoder;
20+
use Symfony\Component\Serializer\Encoder\XmlEncoder;
1921
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
2022
use Symfony\Component\Serializer\Exception\LogicException;
2123
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -379,6 +381,61 @@ private function validateAndDenormalize(string $currentClass, string $attribute,
379381
$data = [$data];
380382
}
381383

384+
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
385+
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
386+
// That's why we have to transform the values, if one of these non-string basic datatypes is expected.
387+
//
388+
// This is special to xml and csv format
389+
if (
390+
\is_string($data) && (XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format)
391+
) {
392+
if (
393+
'' === $data && $type->isNullable() && \in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT], true)
394+
) {
395+
return null;
396+
}
397+
398+
switch ($type->getBuiltinType()) {
399+
case Type::BUILTIN_TYPE_BOOL:
400+
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
401+
if ('false' === $data || '0' === $data) {
402+
$data = false;
403+
} elseif ('true' === $data || '1' === $data) {
404+
$data = true;
405+
} else {
406+
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data));
407+
}
408+
break;
409+
case Type::BUILTIN_TYPE_INT:
410+
if (
411+
ctype_digit($data) ||
412+
'-' === $data[0] && ctype_digit(substr($data, 1))
413+
) {
414+
$data = (int) $data;
415+
} else {
416+
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data));
417+
}
418+
break;
419+
case Type::BUILTIN_TYPE_FLOAT:
420+
if (is_numeric($data)) {
421+
return (float) $data;
422+
}
423+
424+
switch ($data) {
425+
case 'NaN':
426+
return NAN;
427+
case 'INF':
428+
return INF;
429+
case '-INF':
430+
return -INF;
431+
default:
432+
throw new NotNormalizableValueException(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data));
433+
}
434+
435+
break;
436+
}
437+
}
438+
382439
if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
383440
$builtinType = Type::BUILTIN_TYPE_OBJECT;
384441
$class = $collectionValueType->getClassName().'[]';

src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,79 @@ public function getTypeForMappedObject($object): ?string
273273
$this->assertInstanceOf(AbstractDummySecondChild::class, $denormalizedData);
274274
}
275275

276+
public function testDenormalizeBasicTypePropertiesFromXml()
277+
{
278+
$denormalizer = $this->getDenormalizerForObjectWithBasicProperties();
279+
280+
// bool
281+
$objectWithBooleanProperties = $denormalizer->denormalize(
282+
[
283+
'boolTrue1' => 'true',
284+
'boolFalse1' => 'false',
285+
'boolTrue2' => '1',
286+
'boolFalse2' => '0',
287+
'int1' => '4711',
288+
'int2' => '-4711',
289+
'float1' => '123.456',
290+
'float2' => '-1.2344e56',
291+
'float3' => '45E-6',
292+
'floatNaN' => 'NaN',
293+
'floatInf' => 'INF',
294+
'floatNegInf' => '-INF',
295+
],
296+
ObjectWithBasicProperties::class,
297+
'xml'
298+
);
299+
300+
$this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBooleanProperties);
301+
302+
// Bool Properties
303+
$this->assertTrue($objectWithBooleanProperties->boolTrue1);
304+
$this->assertFalse($objectWithBooleanProperties->boolFalse1);
305+
$this->assertTrue($objectWithBooleanProperties->boolTrue2);
306+
$this->assertFalse($objectWithBooleanProperties->boolFalse2);
307+
308+
// Integer Properties
309+
$this->assertEquals(4711, $objectWithBooleanProperties->int1);
310+
$this->assertEquals(-4711, $objectWithBooleanProperties->int2);
311+
312+
// Float Properties
313+
$this->assertEqualsWithDelta(123.456, $objectWithBooleanProperties->float1, 0.01);
314+
$this->assertEqualsWithDelta(-1.2344e56, $objectWithBooleanProperties->float2, 1);
315+
$this->assertEqualsWithDelta(45E-6, $objectWithBooleanProperties->float3, 1);
316+
$this->assertNan($objectWithBooleanProperties->floatNaN);
317+
$this->assertInfinite($objectWithBooleanProperties->floatInf);
318+
$this->assertEquals(-INF, $objectWithBooleanProperties->floatNegInf);
319+
}
320+
321+
private function getDenormalizerForObjectWithBasicProperties()
322+
{
323+
$extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock();
324+
$extractor->method('getTypes')
325+
->will($this->onConsecutiveCalls(
326+
[new Type('bool')],
327+
[new Type('bool')],
328+
[new Type('bool')],
329+
[new Type('bool')],
330+
[new Type('int')],
331+
[new Type('int')],
332+
[new Type('float')],
333+
[new Type('float')],
334+
[new Type('float')],
335+
[new Type('float')],
336+
[new Type('float')],
337+
[new Type('float')]
338+
));
339+
340+
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
341+
$arrayDenormalizer = new ArrayDenormalizerDummy();
342+
$serializer = new SerializerCollectionDummy([$arrayDenormalizer, $denormalizer]);
343+
$arrayDenormalizer->setSerializer($serializer);
344+
$denormalizer->setSerializer($serializer);
345+
346+
return $denormalizer;
347+
}
348+
276349
/**
277350
* Test that additional attributes throw an exception if no metadata factory is specified.
278351
*/
@@ -359,6 +432,45 @@ protected function setAttributeValue(object $object, string $attribute, $value,
359432
}
360433
}
361434

435+
class ObjectWithBasicProperties
436+
{
437+
/** @var bool */
438+
public $boolTrue1;
439+
440+
/** @var bool */
441+
public $boolFalse1;
442+
443+
/** @var bool */
444+
public $boolTrue2;
445+
446+
/** @var bool */
447+
public $boolFalse2;
448+
449+
/** @var int */
450+
public $int1;
451+
452+
/** @var int */
453+
public $int2;
454+
455+
/** @var float */
456+
public $float1;
457+
458+
/** @var float */
459+
public $float2;
460+
461+
/** @var float */
462+
public $float3;
463+
464+
/** @var float */
465+
public $floatNaN;
466+
467+
/** @var float */
468+
public $floatInf;
469+
470+
/** @var float */
471+
public $floatNegInf;
472+
}
473+
362474
class StringCollection
363475
{
364476
/** @var string[] */

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy