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

Lines changed: 50 additions & 0 deletions
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

Lines changed: 112 additions & 0 deletions
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)
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