Skip to content

Commit d50a17b

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 91f6e69 commit d50a17b

File tree

11 files changed

+114
-5
lines changed

11 files changed

+114
-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_iterable($data)) {
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_iterable($value)) {
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(iterable $data)
223223
{
224224
$headers = array();
225225
$flippedHeaders = array();

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

Lines changed: 7 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.
@@ -25,6 +26,8 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
2526
const FORMAT = 'yaml';
2627
const ALTERNATIVE_FORMAT = 'yml';
2728

29+
const ARRAY_OBJECTS_KEY = 'array_objects';
30+
2831
private $dumper;
2932
private $parser;
3033
private $defaultContext = array('yaml_inline' => 0, 'yaml_indent' => 0, 'yaml_flags' => 0);
@@ -47,6 +50,10 @@ public function encode($data, $format, array $context = array())
4750
{
4851
$context = array_merge($this->defaultContext, $context);
4952

53+
if (isset($context[self::ARRAY_OBJECTS_KEY])) {
54+
$context['yaml_flags'] |= Yaml::DUMP_OBJECT_AS_MAP;
55+
}
56+
5057
return $this->dumper->dump($data, $context['yaml_inline'], $context['yaml_indent'], $context['yaml_flags']);
5158
}
5259

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
3737
const DISABLE_TYPE_ENFORCEMENT = 'disable_type_enforcement';
3838
const SKIP_NULL_VALUES = 'skip_null_values';
3939

40+
const ARRAY_OBJECTS_KEY = 'array_objects';
41+
4042
private $propertyTypeExtractor;
4143
private $typesCache = array();
4244
private $attributesCache = array();
@@ -122,6 +124,10 @@ public function normalize($object, $format = null, array $context = array())
122124
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)), $class, $format, $context);
123125
}
124126

127+
if (isset($context[self::ARRAY_OBJECTS_KEY]) && !\count($data)) {
128+
return new \ArrayObject();
129+
}
130+
125131
return $data;
126132
}
127133

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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,25 @@ public function testSkipNullValues()
171171
$result = $normalizer->normalize($dummy, null, array(AbstractObjectNormalizer::SKIP_NULL_VALUES => true));
172172
$this->assertSame(array('bar' => 'present'), $result);
173173
}
174+
175+
public function testNormalizeEmptyObject()
176+
{
177+
$normalizer = new AbstractObjectNormalizerDummy();
178+
179+
// This results in objects turning into arrays in some encoders
180+
$normalizedData = $normalizer->normalize(new EmptyDummy());
181+
$this->assertEquals(array(), $normalizedData);
182+
183+
$normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', array('array_objects' => true));
184+
$this->assertEquals(new \ArrayObject(), $normalizedData);
185+
}
174186
}
175187

176188
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
177189
{
178190
protected function extractAttributes($object, $format = null, array $context = array())
179191
{
192+
return array();
180193
}
181194

182195
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
@@ -206,6 +219,10 @@ class Dummy
206219
public $baz;
207220
}
208221

222+
class EmptyDummy
223+
{
224+
}
225+
209226
class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
210227
{
211228
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
@@ -156,6 +156,11 @@ public function testIgnoredAttributes()
156156
array(),
157157
$this->normalizer->normalize($obj, 'any')
158158
);
159+
160+
$this->assertEquals(
161+
new \ArrayObject(),
162+
$this->normalizer->normalize($obj, 'any', array('array_objects' => true))
163+
);
159164
}
160165

161166
public function testGroupsNormalize()

0 commit comments

Comments
 (0)