diff --git a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php
index 7333e8015a..64626084ee 100644
--- a/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php
+++ b/src/Symfony/Component/Serializer/Encoder/CsvEncoder.php
@@ -69,7 +69,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
{
$handle = fopen('php://temp,', 'w+');
- if (!\is_array($data)) {
+ if (!is_iterable($data)) {
$data = [[$data]];
} elseif (empty($data)) {
$data = [[]];
@@ -210,10 +210,10 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
/**
* Flattens an array and generates keys including the path.
*/
- private function flatten(array $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
+ private function flatten(iterable $array, array &$result, string $keySeparator, string $parentKey = '', bool $escapeFormulas = false)
{
foreach ($array as $key => $value) {
- if (\is_array($value)) {
+ if (is_iterable($value)) {
$this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator, $escapeFormulas);
} else {
if ($escapeFormulas && \in_array(substr((string) $value, 0, 1), $this->formulasStartCharacters, true)) {
@@ -245,7 +245,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
/**
* @return string[]
*/
- private function extractHeaders(array $data): array
+ private function extractHeaders(iterable $data): array
{
$headers = [];
$flippedHeaders = [];
diff --git a/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php b/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php
index dc0bf7fe41..d17ba6b355 100644
--- a/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php
+++ b/src/Symfony/Component/Serializer/Encoder/YamlEncoder.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\Serializer\Encoder;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\Yaml\Parser;
+use Symfony\Component\Yaml\Yaml;
/**
* Encodes YAML data.
@@ -25,6 +26,8 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
const FORMAT = 'yaml';
private const ALTERNATIVE_FORMAT = 'yml';
+ public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects';
+
private $dumper;
private $parser;
private $defaultContext = ['yaml_inline' => 0, 'yaml_indent' => 0, 'yaml_flags' => 0];
@@ -47,6 +50,10 @@ class YamlEncoder implements EncoderInterface, DecoderInterface
{
$context = array_merge($this->defaultContext, $context);
+ if (isset($context[self::PRESERVE_EMPTY_OBJECTS])) {
+ $context['yaml_flags'] |= Yaml::DUMP_OBJECT_AS_MAP;
+ }
+
return $this->dumper->dump($data, $context['yaml_inline'], $context['yaml_indent'], $context['yaml_flags']);
}
diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
index 61311817de..b4195230fb 100644
--- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
@@ -88,6 +88,8 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
*/
public const DEEP_OBJECT_TO_POPULATE = 'deep_object_to_populate';
+ public const PRESERVE_EMPTY_OBJECTS = 'preserve_empty_objects';
+
private $propertyTypeExtractor;
private $typesCache = [];
private $attributesCache = [];
@@ -206,6 +208,10 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute, $format)), $class, $format, $context);
}
+ if (isset($context[self::PRESERVE_EMPTY_OBJECTS]) && !\count($data)) {
+ return new \ArrayObject();
+ }
+
return $data;
}
diff --git a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php
index 02a2118584..619f2fee31 100644
--- a/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php
+++ b/src/Symfony/Component/Serializer/Normalizer/NormalizerInterface.php
@@ -30,7 +30,7 @@ interface NormalizerInterface
* @param string $format Format the normalization result will be encoded as
* @param array $context Context options for the normalizer
*
- * @return array|string|int|float|bool
+ * @return array|string|int|float|bool|\ArrayObject \ArrayObject is used to make sure an empty object is encoded as an object not an array
*
* @throws InvalidArgumentException Occurs when the object given is not an attempted type for the normalizer
* @throws CircularReferenceException Occurs when the normalizer detects a circular reference when no circular
diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php
index 0f93a99cd9..f770535456 100644
--- a/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Encoder/CsvEncoderTest.php
@@ -339,6 +339,43 @@ CSV
]));
}
+ public function testEncodeArrayObject()
+ {
+ $value = new \ArrayObject(['foo' => 'hello', 'bar' => 'hey ho']);
+
+ $this->assertEquals(<<<'CSV'
+foo,bar
+hello,"hey ho"
+
+CSV
+ , $this->encoder->encode($value, 'csv'));
+
+ $value = new \ArrayObject();
+
+ $this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
+ }
+
+ public function testEncodeNestedArrayObject()
+ {
+ $value = new \ArrayObject(['foo' => new \ArrayObject(['nested' => 'value']), 'bar' => new \ArrayObject(['another' => 'word'])]);
+
+ $this->assertEquals(<<<'CSV'
+foo.nested,bar.another
+value,word
+
+CSV
+ , $this->encoder->encode($value, 'csv'));
+ }
+
+ public function testEncodeEmptyArrayObject()
+ {
+ $value = new \ArrayObject();
+ $this->assertEquals("\n", $this->encoder->encode($value, 'csv'));
+
+ $value = ['foo' => new \ArrayObject()];
+ $this->assertEquals("\n\n", $this->encoder->encode($value, 'csv'));
+ }
+
public function testSupportsDecoding()
{
$this->assertTrue($this->encoder->supportsDecoding('csv'));
diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php
index c79b9bd945..0ddaf79e95 100644
--- a/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Encoder/JsonEncodeTest.php
@@ -46,6 +46,8 @@ class JsonEncodeTest extends TestCase
return [
[[], '[]', []],
[[], '{}', ['json_encode_options' => JSON_FORCE_OBJECT]],
+ [new \ArrayObject(), '{}', []],
+ [new \ArrayObject(['foo' => 'bar']), '{"foo":"bar"}', []],
];
}
diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
index 8a48af7d78..55da0933eb 100644
--- a/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php
@@ -48,6 +48,26 @@ class XmlEncoderTest extends TestCase
$this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
}
+ public function testEncodeArrayObject()
+ {
+ $obj = new \ArrayObject(['foo' => 'bar']);
+
+ $expected = ''."\n".
+ 'bar'."\n";
+
+ $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
+ }
+
+ public function testEncodeEmptyArrayObject()
+ {
+ $obj = new \ArrayObject();
+
+ $expected = ''."\n".
+ ''."\n";
+
+ $this->assertEquals($expected, $this->encoder->encode($obj, 'xml'));
+ }
+
/**
* @group legacy
*/
diff --git a/src/Symfony/Component/Serializer/Tests/Encoder/YamlEncoderTest.php b/src/Symfony/Component/Serializer/Tests/Encoder/YamlEncoderTest.php
index 2c4e2bf112..27b98eabb9 100644
--- a/src/Symfony/Component/Serializer/Tests/Encoder/YamlEncoderTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Encoder/YamlEncoderTest.php
@@ -28,6 +28,8 @@ class YamlEncoderTest extends TestCase
$this->assertEquals('foo', $encoder->encode('foo', 'yaml'));
$this->assertEquals('{ foo: 1 }', $encoder->encode(['foo' => 1], 'yaml'));
+ $this->assertEquals('null', $encoder->encode(new \ArrayObject(['foo' => 1]), 'yaml'));
+ $this->assertEquals('{ foo: 1 }', $encoder->encode(new \ArrayObject(['foo' => 1]), 'yaml', ['preserve_empty_objects' => true]));
}
public function testSupportsEncoding()
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
index ed83dc56f2..d4a63ac824 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php
@@ -198,12 +198,25 @@ class AbstractObjectNormalizerTest extends TestCase
'allow_extra_attributes' => false,
]);
}
+
+ public function testNormalizeEmptyObject()
+ {
+ $normalizer = new AbstractObjectNormalizerDummy();
+
+ // This results in objects turning into arrays in some encoders
+ $normalizedData = $normalizer->normalize(new EmptyDummy());
+ $this->assertEquals([], $normalizedData);
+
+ $normalizedData = $normalizer->normalize(new EmptyDummy(), 'any', ['preserve_empty_objects' => true]);
+ $this->assertEquals(new \ArrayObject(), $normalizedData);
+ }
}
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
{
protected function extractAttributes($object, $format = null, array $context = [])
{
+ return [];
}
protected function getAttributeValue($object, $attribute, $format = null, array $context = [])
@@ -233,6 +246,10 @@ class Dummy
public $baz;
}
+class EmptyDummy
+{
+}
+
class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
{
public function __construct()
diff --git a/src/Symfony/Component/Serializer/Tests/SerializerTest.php b/src/Symfony/Component/Serializer/Tests/SerializerTest.php
index 27e89e1f34..aed4842ee0 100644
--- a/src/Symfony/Component/Serializer/Tests/SerializerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/SerializerTest.php
@@ -194,6 +194,19 @@ class SerializerTest extends TestCase
$this->assertEquals(json_encode($data), $result);
}
+ public function testSerializeEmpty()
+ {
+ $serializer = new Serializer([new ObjectNormalizer()], ['json' => new JsonEncoder()]);
+ $data = ['foo' => new \stdClass()];
+
+ //Old buggy behaviour
+ $result = $serializer->serialize($data, 'json');
+ $this->assertEquals('{"foo":[]}', $result);
+
+ $result = $serializer->serialize($data, 'json', ['preserve_empty_objects' => true]);
+ $this->assertEquals('{"foo":{}}', $result);
+ }
+
public function testSerializeNoEncoder()
{
$this->expectException('Symfony\Component\Serializer\Exception\UnexpectedValueException');