Skip to content

Commit 8bb0b30

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 9ad492f commit 8bb0b30

File tree

8 files changed

+104
-3
lines changed

8 files changed

+104
-3
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
1515
use Symfony\Component\Serializer\SerializerAwareInterface;
1616
use Symfony\Component\Serializer\SerializerAwareTrait;
17+
use Symfony\Component\Serializer\Util\EmptyObject;
1718

1819
/**
1920
* Encodes XML data.
@@ -469,6 +470,8 @@ private function selectNodeType(\DOMNode $node, $val): bool
469470
$node->appendChild($child);
470471
} elseif ($val instanceof \Traversable) {
471472
$this->buildXml($node, $val);
473+
} elseif ($val instanceof EmptyObject) {
474+
// Do nothing
472475
} elseif (\is_object($val)) {
473476
return $this->selectNodeType($node, $this->serializer->normalize($val, $this->format, $this->context));
474477
} elseif (is_numeric($val)) {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorResolverInterface;
2525
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
2626
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
27+
use Symfony\Component\Serializer\Util\EmptyObject;
2728

2829
/**
2930
* Base class for a normalizer dealing with objects.
@@ -119,6 +120,10 @@ public function normalize($object, $format = null, array $context = array())
119120
$data = $this->updateData($data, $attribute, $this->serializer->normalize($attributeValue, $format, $this->createChildContext($context, $attribute)));
120121
}
121122

123+
if (!\count($data)) {
124+
return new EmptyObject();
125+
}
126+
122127
return $data;
123128
}
124129

src/Symfony/Component/Serializer/Serializer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
2828
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
2929
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
30+
use Symfony\Component\Serializer\Util\EmptyObject;
3031

3132
/**
3233
* Serializer serializes and deserializes data.
@@ -138,7 +139,7 @@ public function normalize($data, $format = null, array $context = array())
138139
return $normalizer->normalize($data, $format, $context);
139140
}
140141

141-
if (null === $data || is_scalar($data)) {
142+
if (null === $data || is_scalar($data) || $data instanceof EmptyObject) {
142143
return $data;
143144
}
144145

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
2424
use Symfony\Component\Serializer\SerializerAwareInterface;
2525
use Symfony\Component\Serializer\SerializerInterface;
26+
use Symfony\Component\Serializer\Util\EmptyObject;
2627

2728
class AbstractObjectNormalizerTest extends TestCase
2829
{
@@ -161,12 +162,21 @@ public function testExtraAttributesException()
161162
'allow_extra_attributes' => false,
162163
));
163164
}
165+
166+
public function testNormalizeEmptyObject()
167+
{
168+
$normalizer = new AbstractObjectNormalizerDummy();
169+
$normalizedData = $normalizer->normalize(new EmptyDummy());
170+
171+
$this->assertEquals(new EmptyObject(), $normalizedData);
172+
}
164173
}
165174

166175
class AbstractObjectNormalizerDummy extends AbstractObjectNormalizer
167176
{
168177
protected function extractAttributes($object, $format = null, array $context = array())
169178
{
179+
return array();
170180
}
171181

172182
protected function getAttributeValue($object, $attribute, $format = null, array $context = array())
@@ -196,6 +206,10 @@ class Dummy
196206
public $baz;
197207
}
198208

209+
class EmptyDummy
210+
{
211+
}
212+
199213
class AbstractObjectNormalizerWithMetadata extends AbstractObjectNormalizer
200214
{
201215
public function __construct()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\Component\Serializer\Tests\Fixtures\GroupDummy;
3030
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
3131
use Symfony\Component\Serializer\Tests\Fixtures\SiblingHolder;
32+
use Symfony\Component\Serializer\Util\EmptyObject;
3233

3334
/**
3435
* @author Kévin Dunglas <dunglas@gmail.com>
@@ -319,7 +320,7 @@ public function testNormalizeNoPropertyInGroup()
319320
$obj = new GroupDummy();
320321
$obj->setFoo('foo');
321322

322-
$this->assertEquals(array(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist'))));
323+
$this->assertEquals(new EmptyObject(), $this->normalizer->normalize($obj, null, array('groups' => array('notExist'))));
323324
}
324325

325326
public function testGroupsNormalizeWithNameConverter()

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
2525
use Symfony\Component\Serializer\Tests\Fixtures\PropertyCircularReferenceDummy;
2626
use Symfony\Component\Serializer\Tests\Fixtures\PropertySiblingHolder;
27+
use Symfony\Component\Serializer\Util\EmptyObject;
2728

2829
class PropertyNormalizerTest extends TestCase
2930
{
@@ -154,7 +155,7 @@ public function testIgnoredAttributes()
154155
$obj->setBar('bar');
155156

156157
$this->assertEquals(
157-
array(),
158+
new EmptyObject(),
158159
$this->normalizer->normalize($obj, 'any')
159160
);
160161
}

src/Symfony/Component/Serializer/Tests/SerializerTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
use Doctrine\Common\Annotations\AnnotationReader;
1515
use PHPUnit\Framework\TestCase;
1616
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
17+
use Symfony\Component\Serializer\Encoder\CsvEncoder;
1718
use Symfony\Component\Serializer\Encoder\JsonEncoder;
19+
use Symfony\Component\Serializer\Encoder\XmlEncoder;
20+
use Symfony\Component\Serializer\Encoder\YamlEncoder;
1821
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
1922
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
2023
use Symfony\Component\Serializer\Mapping\ClassMetadata;
@@ -182,6 +185,48 @@ public function testSerializeArrayOfScalars()
182185
$this->assertEquals(json_encode($data), $result);
183186
}
184187

188+
public function testSerializeEmpty()
189+
{
190+
$serializer = new Serializer(array(new ObjectNormalizer()), array('json' => new JsonEncoder()));
191+
$data = array('foo' => new \stdClass());
192+
$result = $serializer->serialize($data, 'json');
193+
$this->assertEquals('{"foo":{}}', $result);
194+
}
195+
196+
public function testSerializeEmptyYaml()
197+
{
198+
$serializer = new Serializer(array(new ObjectNormalizer()), array('yaml' => new YamlEncoder()));
199+
$data = array('foo' => new \stdClass());
200+
$result = $serializer->serialize($data, 'yaml');
201+
$this->assertEquals('{ foo: null }', $result);
202+
}
203+
204+
public function testSerializeEmptyXml()
205+
{
206+
$serializer = new Serializer(array(new ObjectNormalizer()), array('xml' => new XmlEncoder()));
207+
$data = array('foo' => new \stdClass());
208+
$result = $serializer->serialize($data, 'xml');
209+
$this->assertEquals(<<<'XML'
210+
<?xml version="1.0"?>
211+
<response><foo/></response>
212+
213+
XML
214+
, $result);
215+
}
216+
217+
public function testSerializeEmptyCsv()
218+
{
219+
$serializer = new Serializer(array(new ObjectNormalizer()), array('csv' => new CsvEncoder()));
220+
$data = array('foo' => new \stdClass(), 'bar' => new \stdClass());
221+
$result = $serializer->serialize($data, 'csv');
222+
$this->assertEquals(<<<'CSV'
223+
foo,bar
224+
,
225+
226+
CSV
227+
, $result);
228+
}
229+
185230
/**
186231
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
187232
*/
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Util;
13+
14+
/**
15+
* Represents an empty object that has already been normalized
16+
* Used to make sure you get {} instead of [] in the output.
17+
*
18+
* @author Fred Cox <mcfedr@gmail.com>
19+
*/
20+
class EmptyObject
21+
{
22+
/**
23+
* This value is used by the CsvEncoder.
24+
*
25+
* @return string
26+
*/
27+
public function __toString()
28+
{
29+
return '';
30+
}
31+
}

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