Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Serializer] Remove support for returning empty, iterable, countable, raw object when normalizing #42699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Component/Serializer/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ CHANGELOG

* Remove `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead
* Remove the ability to create instances of the annotation classes by passing an array of parameters, use named arguments instead
* Remove support for returning empty, iterable, countable, raw object when normalizing

5.4
---
Expand Down
16 changes: 5 additions & 11 deletions src/Symfony/Component/Serializer/Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,11 @@ public function normalize(mixed $data, string $format = null, array $context = [
}

if (is_iterable($data)) {
if (is_countable($data) && 0 === \count($data)) {
switch (true) {
case ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && \is_object($data):
if (!$data instanceof \ArrayObject) {
trigger_deprecation('symfony/serializer', '5.4', 'Returning empty object of class "%s" from "%s()" is deprecated. This class should extend "ArrayObject".', get_debug_type($data), __METHOD__);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reading, I think we should return the original object when it extends ArrayObject

Copy link
Member Author

@lyrixx lyrixx Aug 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree. It produces really bad behavior that I explained in your PR.
See the current diff on the test suite to understand.

- "g1":{"list":{"list":[]},"settings":[]},"g2":{"list":["greg"],"settings":[]}}';
+ "g1":{"list":{},"settings":[]},"g2":{"list":["greg"],"settings":[]}}';

before: There is an inner list when the list is empty, but it does not exist when the list is not empty
after: the shape is always the same, whereas the list is empty or not

=> This is a bug fix

Copy link
Member

@nicolas-grekas nicolas-grekas Aug 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a behavior change that we'd better not do imho. The type hint doesn't require this.
Or we'd need another deprecation on 5.4, but I don't see why we'd do this (especially since there is a test about this).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test BTW, to show how it behaves.
I thought it was clear in the previous conversation... 😬

I can do what you want, but I persist to say it's a bad idea, since the serializer is not doing what people expect!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #42730 as follow up of this discussion.

}

return $data;
case ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false) && \is_array($data):
return new \ArrayObject();
}
if (is_countable($data) && 0 === \count($data) && (
($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && \is_object($data) ||
($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false) && \is_array($data)
)) {
return new \ArrayObject();
}

$normalized = [];
Expand Down
91 changes: 2 additions & 89 deletions src/Symfony/Component/Serializer/Tests/SerializerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ public function testNormalizeWithCollection(Serializer $serializer, array $data)
/** @dataProvider provideObjectOrCollectionTests */
public function testNormalizePreserveEmptyArrayObject(Serializer $serializer, array $data)
{
$expected = '{"a1":{},"a2":{"k":"v"},"b1":[],"b2":{"k":"v"},"c1":{"nested":{}},"c2":{"nested":{"k":"v"}},"d1":{"nested":[]},"d2":{"nested":{"k":"v"}},"e1":{"map":[]},"e2":{"map":{"k":"v"}},"f1":{"map":{}},"f2":{"map":{"k":"v"}},"g1":{"list":{"list":[]},"settings":[]},"g2":{"list":["greg"],"settings":[]}}';
$expected = '{"a1":{},"a2":{"k":"v"},"b1":[],"b2":{"k":"v"},"c1":{"nested":{}},"c2":{"nested":{"k":"v"}},"d1":{"nested":[]},"d2":{"nested":{"k":"v"}},"e1":{"map":[]},"e2":{"map":{"k":"v"}},"f1":{"map":{}},"f2":{"map":{"k":"v"}},"g1":{"list":{},"settings":[]},"g2":{"list":["greg"],"settings":[]}}';
$this->assertSame($expected, $serializer->serialize($data, 'json', [
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
]));
Expand All @@ -599,62 +599,7 @@ public function testNormalizeEmptyArrayAsObject(Serializer $serializer, array $d
/** @dataProvider provideObjectOrCollectionTests */
public function testNormalizeEmptyArrayAsObjectAndPreserveEmptyArrayObject(Serializer $serializer, array $data)
{
$expected = '{"a1":{},"a2":{"k":"v"},"b1":{},"b2":{"k":"v"},"c1":{"nested":{}},"c2":{"nested":{"k":"v"}},"d1":{"nested":{}},"d2":{"nested":{"k":"v"}},"e1":{"map":{}},"e2":{"map":{"k":"v"}},"f1":{"map":{}},"f2":{"map":{"k":"v"}},"g1":{"list":{"list":[]},"settings":{}},"g2":{"list":["greg"],"settings":{}}}';
$this->assertSame($expected, $serializer->serialize($data, 'json', [
Serializer::EMPTY_ARRAY_AS_OBJECT => true,
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
]));
}

/**
* @dataProvider provideObjectOrCollectionTests
* @group legacy
*/
public function testNormalizeWithCollectionLegacy(Serializer $serializer, array $data)
{
$data['g1'] = new BazLegacy([]);
$data['g2'] = new BazLegacy(['greg']);
$expected = '{"a1":[],"a2":{"k":"v"},"b1":[],"b2":{"k":"v"},"c1":{"nested":[]},"c2":{"nested":{"k":"v"}},"d1":{"nested":[]},"d2":{"nested":{"k":"v"}},"e1":{"map":[]},"e2":{"map":{"k":"v"}},"f1":{"map":[]},"f2":{"map":{"k":"v"}},"g1":{"list":[],"settings":[]},"g2":{"list":["greg"],"settings":[]}}';
$this->assertSame($expected, $serializer->serialize($data, 'json'));
}

/**
* @dataProvider provideObjectOrCollectionTests
* @group legacy
*/
public function testNormalizePreserveEmptyArrayObjectLegacy(Serializer $serializer, array $data)
{
$data['g1'] = new BazLegacy([]);
$data['g2'] = new BazLegacy(['greg']);
$expected = '{"a1":{},"a2":{"k":"v"},"b1":[],"b2":{"k":"v"},"c1":{"nested":{}},"c2":{"nested":{"k":"v"}},"d1":{"nested":[]},"d2":{"nested":{"k":"v"}},"e1":{"map":[]},"e2":{"map":{"k":"v"}},"f1":{"map":{}},"f2":{"map":{"k":"v"}},"g1":{"list":{"list":[]},"settings":[]},"g2":{"list":["greg"],"settings":[]}}';
$this->assertSame($expected, $serializer->serialize($data, 'json', [
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
]));
}

/**
* @dataProvider provideObjectOrCollectionTests
* @group legacy
*/
public function testNormalizeEmptyArrayAsObjectLegacy(Serializer $serializer, array $data)
{
$data['g1'] = new BazLegacy([]);
$data['g2'] = new BazLegacy(['greg']);
$expected = '{"a1":[],"a2":{"k":"v"},"b1":{},"b2":{"k":"v"},"c1":{"nested":[]},"c2":{"nested":{"k":"v"}},"d1":{"nested":{}},"d2":{"nested":{"k":"v"}},"e1":{"map":{}},"e2":{"map":{"k":"v"}},"f1":{"map":[]},"f2":{"map":{"k":"v"}},"g1":{"list":[],"settings":{}},"g2":{"list":["greg"],"settings":{}}}';
$this->assertSame($expected, $serializer->serialize($data, 'json', [
Serializer::EMPTY_ARRAY_AS_OBJECT => true,
]));
}

/**
* @dataProvider provideObjectOrCollectionTests
* @group legacy
*/
public function testNormalizeEmptyArrayAsObjectAndPreserveEmptyArrayObjectLegacy(Serializer $serializer, array $data)
{
$data['g1'] = new BazLegacy([]);
$data['g2'] = new BazLegacy(['greg']);
$expected = '{"a1":{},"a2":{"k":"v"},"b1":{},"b2":{"k":"v"},"c1":{"nested":{}},"c2":{"nested":{"k":"v"}},"d1":{"nested":{}},"d2":{"nested":{"k":"v"}},"e1":{"map":{}},"e2":{"map":{"k":"v"}},"f1":{"map":{}},"f2":{"map":{"k":"v"}},"g1":{"list":{"list":[]},"settings":{}},"g2":{"list":["greg"],"settings":{}}}';
$expected = '{"a1":{},"a2":{"k":"v"},"b1":{},"b2":{"k":"v"},"c1":{"nested":{}},"c2":{"nested":{"k":"v"}},"d1":{"nested":{}},"d2":{"nested":{"k":"v"}},"e1":{"map":{}},"e2":{"map":{"k":"v"}},"f1":{"map":{}},"f2":{"map":{"k":"v"}},"g1":{"list":{},"settings":{}},"g2":{"list":["greg"],"settings":{}}}';
$this->assertSame($expected, $serializer->serialize($data, 'json', [
Serializer::EMPTY_ARRAY_AS_OBJECT => true,
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
Expand Down Expand Up @@ -868,38 +813,6 @@ public function getIterator(): \Traversable
}
}

class BazLegacy
{
public $list;

public $settings = [];

public function __construct(array $list)
{
$this->list = new DummyListLegacy($list);
}
}

class DummyListLegacy implements \Countable, \IteratorAggregate
{
public $list;

public function __construct(array $list)
{
$this->list = $list;
}

public function count(): int
{
return \count($this->list);
}

public function getIterator(): \Traversable
{
return new \ArrayIterator($this->list);
}
}

interface NormalizerAwareNormalizer extends NormalizerInterface, NormalizerAwareInterface
{
}
Expand Down