Skip to content

Commit 889fa6a

Browse files
committed
[Serializer] Encode empty objects as objects, not arrays
Allows Normalizers to return a representation of an empty object that the encoder recognizes as such.
1 parent c3fd60d commit 889fa6a

File tree

11 files changed

+111
-5
lines changed

11 files changed

+111
-5
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public function encode($data, $format, array $context = array())
5353
{
5454
$handle = fopen('php://temp,', 'w+');
5555

56-
if (!\is_array($data)) {
56+
if (!(\is_array($data) || $data instanceof \Traversable)) {
5757
$data = array(array($data));
5858
} elseif (empty($data)) {
5959
$data = array(array());
@@ -185,10 +185,10 @@ public function supportsDecoding($format)
185185
/**
186186
* Flattens an array and generates keys including the path.
187187
*/
188-
private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
188+
private function flatten($array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
189189
{
190190
foreach ($array as $key => $value) {
191-
if (\is_array($value)) {
191+
if (\is_array($value) || $value instanceof \Traversable) {
192192
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator, $escapeFormulas);
193193
} else {
194194
if ($escapeFormulas && \in_array(substr($value, 0, 1), $this->formulasStartCharacters, true)) {
@@ -219,7 +219,7 @@ private function getCsvOptions(array $context)
219219
/**
220220
* @return string[]
221221
*/
222-
private function extractHeaders(array $data)
222+
private function extractHeaders($data)
223223
{
224224
$headers = array();
225225
$flippedHeaders = array();

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Serializer\Exception\RuntimeException;
1515
use Symfony\Component\Yaml\Dumper;
1616
use Symfony\Component\Yaml\Parser;
17+
use Symfony\Component\Yaml\Yaml;
1718

1819
/**
1920
* Encodes YAML data.
@@ -46,6 +47,10 @@ public function encode($data, $format, array $context = array())
4647
{
4748
$context = array_merge($this->defaultContext, $context);
4849

50+
if (isset($context['array_objects'])) {
51+
$context['yaml_flags'] |= Yaml::DUMP_OBJECT_AS_MAP;
52+
}
53+
4954
return $this->dumper->dump($data, $context['yaml_inline'], $context['yaml_indent'], $context['yaml_flags']);
5055
}
5156

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ public function normalize($object, $format = null, array $context = array())
119119
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)), $class, $format, $context);
120120
}
121121

122+
if (isset($context['array_objects']) && !\count($data)) {
123+
return new \ArrayObject();
124+
}
125+
122126
return $data;
123127
}
124128

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface NormalizerInterface
2929
* @param string $format Format the normalization result will be encoded as
3030
* @param array $context Context options for the normalizer
3131
*
32-
* @return array|string|int|float|bool
32+
* @return array|string|int|float|bool|\ArrayObject
3333
*
3434
* @throws InvalidArgumentException Occurs when the object given is not an attempted type for the normalizer
3535
* @throws CircularReferenceException Occurs when the normalizer detects a circular reference when no circular

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,43 @@ public function testEncodeFormulasWithSettingsPassedInContext()
276276
)));
277277
}
278278

279+
public function testEncodeArrayObject()
280+
{
281+
$value = new \ArrayObject(array('foo' => 'hello', 'bar' => 'hey ho'));
282+
283+
$this->assertEquals(<<<'CSV'
284+
foo,bar
285+
hello,"hey ho"
286+
287+
CSV
288+
, $this->encoder->encode($value, 'csv'));
289+
290+
$value = new \ArrayObject();
291+
292+
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
293+
}
294+
295+
public function testEncodeNestedArrayObject()
296+
{
297+
$value = new \ArrayObject(array('foo' => new \ArrayObject(array('nested' => 'value')), 'bar' => new \ArrayObject(array('another' => 'word'))));
298+
299+
$this->assertEquals(<<<'CSV'
300+
foo.nested,bar.another
301+
value,word
302+
303+
CSV
304+
, $this->encoder->encode($value, 'csv'));
305+
}
306+
307+
public function testEncodeEmptyArrayObject()
308+
{
309+
$value = new \ArrayObject();
310+
$this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
311+
312+
$value = array('foo' => new \ArrayObject());
313+
$this->assertEquals("\n\n", $this->encoder->encode($value, 'csv'));
314+
}
315+
279316
public function testSupportsDecoding()
280317
{
281318
$this->assertTrue($this->encoder->supportsDecoding('csv'));

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public function encodeProvider()
4646
return array(
4747
array(array(), '[]', array()),
4848
array(array(), '{}', array('json_encode_options' => JSON_FORCE_OBJECT)),
49+
array(new \ArrayObject(), '{}', array()),
50+
array(new \ArrayObject(array('foo' => 'bar')), '{"foo":"bar"}', array()),
4951
);
5052
}
5153

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ public function testEncodeScalar()
4747
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
4848
}
4949

50+
public function testEncodeArrayObject()
51+
{
52+
$obj = new \ArrayObject(array('foo' => 'bar'));
53+
54+
$expected = '<?xml version="1.0"?>'."\n".
55+
'<response><foo>bar</foo></response>'."\n";
56+
57+
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
58+
}
59+
60+
public function testEncodeEmptyArrayObject()
61+
{
62+
$obj = new \ArrayObject();
63+
64+
$expected = '<?xml version="1.0"?>'."\n".
65+
'<response/>'."\n";
66+
67+
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
68+
}
69+
5070
public function testSetRootNodeName()
5171
{
5272
$obj = new ScalarDummy();

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public function testEncode()
2828

2929
$this->assertEquals('foo', $encoder->encode('foo', 'yaml'));
3030
$this->assertEquals('{ foo: 1 }', $encoder->encode(array('foo' => 1), 'yaml'));
31+
$this->assertEquals('null', $encoder->encode(new \ArrayObject(array('foo' => 1)), 'yaml'));
32+
$this->assertEquals('{ foo: 1 }', $encoder->encode(new \ArrayObject(array('foo' => 1)), 'yaml', array('array_objects' => true)));
3133
}
3234

3335
public function testSupportsEncoding()

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,24 @@ public function testExtraAttributesException()
161161
'allow_extra_attributes' => false,
162162
));
163163
}
164+
165+
public function testNormalizeEmptyObject()
166+
{
167+
$normalizer = new AbstractObjectNormalizerDummy();
168+
169+
$normalizedData = $normalizer->normalize(new EmptyDummy());
170+
$this->assertEquals(array(), $normalizedData);
171+
172+
$normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', array('array_objects' => true));
173+
$this->assertEquals(new \ArrayObject(), $normalizedData);
174+
}
164175
}
165176

166177
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
167178
{
168179
protected function extractAttributes($object, $format = null, array $context = array())
169180
{
181+
return array();
170182
}
171183

172184
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
@@ -196,6 +208,10 @@ class Dummy
196208
public $baz;
197209
}
198210

211+
class EmptyDummy
212+
{
213+
}
214+
199215
class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
200216
{
201217
public function __construct()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ public function testIgnoredAttributes()
157157
array(),
158158
$this->normalizer->normalize($obj, 'any')
159159
);
160+
161+
$this->assertEquals(
162+
new \ArrayObject(),
163+
$this->normalizer->normalize($obj, 'any', array('array_objects' => true))
164+
);
160165
}
161166

162167
public function testGroupsNormalize()

0 commit comments

Comments
 (0)