Skip to content

Commit bbbac21

Browse files
committed
[Serializer] fix denormalization of basic property-types in XML and CSV #33849
1 parent c91e7ca commit bbbac21

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

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

+50
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
use Symfony\Component\PropertyAccess\Exception\InvalidArgumentException;
1515
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1616
use Symfony\Component\PropertyInfo\Type;
17+
use Symfony\Component\Serializer\Encoder\CsvEncoder;
1718
use Symfony\Component\Serializer\Encoder\JsonEncoder;
19+
use Symfony\Component\Serializer\Encoder\XmlEncoder;
1820
use Symfony\Component\Serializer\Exception\ExtraAttributesException;
1921
use Symfony\Component\Serializer\Exception\LogicException;
2022
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
@@ -252,6 +254,54 @@ private function validateAndDenormalize($currentClass, $attribute, $data, $forma
252254
$data = [$data];
253255
}
254256

257+
// In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
258+
// if a value is meant to be a string, float, int or a boolean value from the serialized representation.
259+
// That's why we have to transform the values, if one of these non-string basic datatypes is epxected.
260+
//
261+
// This is special to xml and csv format
262+
if (
263+
(XmlEncoder::FORMAT === $format || CsvEncoder::FORMAT === $format) &&
264+
\in_array($type->getBuiltinType(), [Type::BUILTIN_TYPE_BOOL, Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_FLOAT]) &&
265+
\is_string($data)
266+
) {
267+
switch ($type->getBuiltinType()) {
268+
case Type::BUILTIN_TYPE_BOOL:
269+
// according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
270+
if ('false' === $data || '0' === $data) {
271+
$data = false;
272+
} elseif ('true' === $data || '1' === $data) {
273+
$data = true;
274+
}
275+
break;
276+
case Type::BUILTIN_TYPE_INT:
277+
278+
if (
279+
true === ctype_digit($data) ||
280+
'-' == $data[0] && true === ctype_digit(substr($data, 1))
281+
) {
282+
$data = (int) $data;
283+
}
284+
break;
285+
case Type::BUILTIN_TYPE_FLOAT:
286+
if (is_numeric($data)) {
287+
return '0x' === $data[0].$data[1] ? hexdec($data) : (float) $data;
288+
} elseif (
289+
true === ctype_digit($data) ||
290+
'-' == $data[0] && true === ctype_digit(substr($data, 1))
291+
) {
292+
$data = (int) $data;
293+
} elseif ('NaN' === $data) {
294+
return NAN;
295+
} elseif ('INF') {
296+
return INF;
297+
} elseif ('-INF') {
298+
return -INF;
299+
}
300+
301+
break;
302+
}
303+
}
304+
255305
if (null !== $collectionValueType && Type::BUILTIN_TYPE_OBJECT === $collectionValueType->getBuiltinType()) {
256306
$builtinType = Type::BUILTIN_TYPE_OBJECT;
257307
$class = $collectionValueType->getClassName().'[]';

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

+112
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,79 @@ private function getDenormalizerForStringCollection()
181181
return $denormalizer;
182182
}
183183

184+
public function testDenormalizeBasicTypePropertiesFromXml()
185+
{
186+
$denormalizer = $this->getDenormalizerForObjectWithBasicProperties();
187+
188+
// bool
189+
$objectWithBooleanProperties = $denormalizer->denormalize(
190+
[
191+
'boolTrue1' => 'true',
192+
'boolFalse1' => 'false',
193+
'boolTrue2' => '1',
194+
'boolFalse2' => '0',
195+
'int1' => '4711',
196+
'int2' => '-4711',
197+
'float1' => '123.456',
198+
'float2' => '-1.2344e56',
199+
'float3' => '45E-6',
200+
'floatNaN' => 'NaN',
201+
'floatInf' => 'INF',
202+
'floatNegInf' => '-INF',
203+
],
204+
ObjectWithBasicProperties::class,
205+
'xml'
206+
);
207+
208+
$this->assertInstanceOf(ObjectWithBasicProperties::class, $objectWithBooleanProperties);
209+
210+
// Bool Properties
211+
$this->assertTrue($objectWithBooleanProperties->boolTrue1);
212+
$this->assertFalse($objectWithBooleanProperties->boolFalse1);
213+
$this->assertTrue($objectWithBooleanProperties->boolTrue2);
214+
$this->assertFalse($objectWithBooleanProperties->boolFalse2);
215+
216+
// Integer Properties
217+
$this->assertEquals(4711, $objectWithBooleanProperties->int1);
218+
$this->assertEquals(-4711, $objectWithBooleanProperties->int2);
219+
220+
// Float Properties
221+
$this->assertEqualsWithDelta(123.456, $objectWithBooleanProperties->float1, 0.01);
222+
$this->assertEqualsWithDelta(-1.2344e56, $objectWithBooleanProperties->float2, 1);
223+
$this->assertEqualsWithDelta(45E-6, $objectWithBooleanProperties->float3, 1);
224+
$this->assertNan($objectWithBooleanProperties->floatNaN);
225+
$this->assertInfinite($objectWithBooleanProperties->floatInf);
226+
$this->assertEquals(-INF, $objectWithBooleanProperties->floatNegInf);
227+
}
228+
229+
private function getDenormalizerForObjectWithBasicProperties()
230+
{
231+
$extractor = $this->getMockBuilder(PhpDocExtractor::class)->getMock();
232+
$extractor->method('getTypes')
233+
->will($this->onConsecutiveCalls(
234+
[new Type('bool')],
235+
[new Type('bool')],
236+
[new Type('bool')],
237+
[new Type('bool')],
238+
[new Type('int')],
239+
[new Type('int')],
240+
[new Type('float')],
241+
[new Type('float')],
242+
[new Type('float')],
243+
[new Type('float')],
244+
[new Type('float')],
245+
[new Type('float')]
246+
));
247+
248+
$denormalizer = new AbstractObjectNormalizerCollectionDummy(null, null, $extractor);
249+
$arrayDenormalizer = new ArrayDenormalizerDummy();
250+
$serializer = new SerializerCollectionDummy([$arrayDenormalizer, $denormalizer]);
251+
$arrayDenormalizer->setSerializer($serializer);
252+
$denormalizer->setSerializer($serializer);
253+
254+
return $denormalizer;
255+
}
256+
184257
/**
185258
* Test that additional attributes throw an exception if no metadata factory is specified.
186259
*/
@@ -250,6 +323,45 @@ protected function setAttributeValue($object, $attribute, $value, $format = null
250323
}
251324
}
252325

326+
class ObjectWithBasicProperties
327+
{
328+
/** @var bool */
329+
public $boolTrue1;
330+
331+
/** @var bool */
332+
public $boolFalse1;
333+
334+
/** @var bool */
335+
public $boolTrue2;
336+
337+
/** @var bool */
338+
public $boolFalse2;
339+
340+
/** @var int */
341+
public $int1;
342+
343+
/** @var int */
344+
public $int2;
345+
346+
/** @var float */
347+
public $float1;
348+
349+
/** @var float */
350+
public $float2;
351+
352+
/** @var float */
353+
public $float3;
354+
355+
/** @var float */
356+
public $floatNaN;
357+
358+
/** @var float */
359+
public $floatInf;
360+
361+
/** @var float */
362+
public $floatNegInf;
363+
}
364+
253365
class StringCollection
254366
{
255367
/** @var string[] */

0 commit comments

Comments
 (0)