Skip to content

Commit d89d08f

Browse files
Maximilian Rutanicolas-grekas
authored andcommitted
[Serializer] Add XmlEncoder::PRESERVE_NUMERIC_KEYS context option
1 parent 5eae900 commit d89d08f

File tree

5 files changed

+62
-2
lines changed

5 files changed

+62
-2
lines changed

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add `CDATA_WRAPPING_NAME_PATTERN` support to `XmlEncoder`
1010
* Add support for `can*()` methods to `AttributeLoader`
1111
* Make `AttributeMetadata` and `ClassMetadata` final
12+
* Add `XmlEncoder::PRESERVE_NUMERIC_KEYS` context option
1213
* Deprecate class aliases in the `Annotation` namespace, use attributes instead
1314
* Deprecate getters in attribute classes in favor of public properties
1415
* Deprecate `ClassMetadataFactoryCompiler`

src/Symfony/Component/Serializer/Context/Encoder/XmlEncoderContextBuilder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,12 @@ public function withIgnoreEmptyAttributes(?bool $ignoreEmptyAttributes): static
168168
{
169169
return $this->with(XmlEncoder::IGNORE_EMPTY_ATTRIBUTES, $ignoreEmptyAttributes);
170170
}
171+
172+
/**
173+
* Configures whether to preserve numeric keys in array.
174+
*/
175+
public function withPreserveNumericKeys(?bool $preserveNumericKeys): static
176+
{
177+
return $this->with(XmlEncoder::PRESERVE_NUMERIC_KEYS, $preserveNumericKeys);
178+
}
171179
}

src/Symfony/Component/Serializer/Encoder/XmlEncoder.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
6262
public const CDATA_WRAPPING_NAME_PATTERN = 'cdata_wrapping_name_pattern';
6363
public const CDATA_WRAPPING_PATTERN = 'cdata_wrapping_pattern';
6464
public const IGNORE_EMPTY_ATTRIBUTES = 'ignore_empty_attributes';
65+
public const PRESERVE_NUMERIC_KEYS = 'preserve_numeric_keys';
6566

6667
private array $defaultContext = [
6768
self::AS_COLLECTION => false,
@@ -76,6 +77,7 @@ class XmlEncoder implements EncoderInterface, DecoderInterface, NormalizationAwa
7677
self::CDATA_WRAPPING_NAME_PATTERN => false,
7778
self::CDATA_WRAPPING_PATTERN => '/[<>&]/',
7879
self::IGNORE_EMPTY_ATTRIBUTES => false,
80+
self::PRESERVE_NUMERIC_KEYS => false,
7981
];
8082

8183
public function __construct(array $defaultContext = [])
@@ -347,6 +349,7 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr
347349
{
348350
$append = true;
349351
$removeEmptyTags = $context[self::REMOVE_EMPTY_TAGS] ?? $this->defaultContext[self::REMOVE_EMPTY_TAGS] ?? false;
352+
$preserveNumericKeys = $context[self::PRESERVE_NUMERIC_KEYS] ?? $this->defaultContext[self::PRESERVE_NUMERIC_KEYS] ?? false;
350353
$encoderIgnoredNodeTypes = $context[self::ENCODER_IGNORED_NODE_TYPES] ?? $this->defaultContext[self::ENCODER_IGNORED_NODE_TYPES];
351354

352355
if (\is_array($data) || ($data instanceof \Traversable && (null === $this->serializer || !$this->serializer->supportsNormalization($data, $format)))) {
@@ -373,9 +376,9 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr
373376
if (!\in_array(\XML_COMMENT_NODE, $encoderIgnoredNodeTypes, true)) {
374377
$append = $this->appendComment($parentNode, $data);
375378
}
376-
} elseif (\is_array($data) && false === is_numeric($key)) {
379+
} elseif (\is_array($data) && !is_numeric($key)) {
377380
// Is this array fully numeric keys?
378-
if (ctype_digit(implode('', array_keys($data)))) {
381+
if (!$preserveNumericKeys && null === array_find_key($data, static fn ($v, $k) => is_string($k))) {
379382
/*
380383
* Create nodes to append to $parentNode based on the $key of this array
381384
* Produces <xml><item>0</item><item>1</item></xml>

src/Symfony/Component/Serializer/Mapping/Loader/schema/serialization.schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,10 @@
326326
"type": "boolean",
327327
"description": "Whether to ignore empty attributes (XmlEncoder)"
328328
},
329+
"preserve_numeric_keys": {
330+
"type": "boolean",
331+
"description": "Whether to preserve numeric keys in array (XmlEncoder)"
332+
},
329333
"inline_threshold": {
330334
"type": "integer",
331335
"description": "Threshold to switch to inline YAML (YamlEncoder)"

src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,4 +1033,48 @@ public function testEncodeIgnoringEmptyAttribute()
10331033

10341034
$this->assertEquals($expected, $this->encoder->encode($data, 'xml', ['ignore_empty_attributes' => true]));
10351035
}
1036+
1037+
public function testEncodeArrayAsItem()
1038+
{
1039+
$expected = <<<'XML'
1040+
<?xml version="1.0"?>
1041+
<response><person><item key="0"><firstname>Benjamin</firstname><lastname>Alexandre</lastname></item><item key="1"><firstname>Damien</firstname><lastname>Clay</lastname></item></person></response>
1042+
1043+
XML;
1044+
$source = ['person' => [
1045+
['@key' => 0, 'firstname' => 'Benjamin', 'lastname' => 'Alexandre'],
1046+
['@key' => 1, 'firstname' => 'Damien', 'lastname' => 'Clay'],
1047+
]];
1048+
1049+
$this->assertSame($expected, $this->encoder->encode($source, 'xml', [
1050+
XmlEncoder::PRESERVE_NUMERIC_KEYS => true,
1051+
]));
1052+
}
1053+
1054+
public function testDecodeArrayAsItem()
1055+
{
1056+
$source = <<<'XML'
1057+
<?xml version="1.0"?>
1058+
<response>
1059+
<person>
1060+
<item key="0">
1061+
<firstname>Benjamin</firstname>
1062+
<lastname>Alexandre</lastname>
1063+
</item>
1064+
<item key="1">
1065+
<firstname>Damien</firstname>
1066+
<lastname>Clay</lastname>
1067+
</item>
1068+
</person>
1069+
</response>
1070+
XML;
1071+
$expected = ['person' => [
1072+
['@key' => 0, 'firstname' => 'Benjamin', 'lastname' => 'Alexandre', ],
1073+
['@key' => 1, 'firstname' => 'Damien', 'lastname' => 'Clay', ],
1074+
]];
1075+
1076+
$this->assertSame($expected, $this->encoder->decode($source, 'xml', [
1077+
XmlEncoder::PRESERVE_NUMERIC_KEYS => true,
1078+
]));
1079+
}
10361080
}

0 commit comments

Comments
 (0)