Skip to content

Commit

Permalink
Added default_skip_when_empty option as config using the exclusionStr…
Browse files Browse the repository at this point in the history
…ategy approach

Updated documentation
  • Loading branch information
slava-v authored and slava-v committed Oct 25, 2020
1 parent 8d7113e commit 25ca44d
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 0 deletions.
20 changes: 20 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,23 @@ a serialization context from your callable and use it.
You can also set a default DeserializationContextFactory with
``->setDeserializationContextFactory(function () { /* ... */ })``
to be used with methods ``deserialize()`` and ``fromArray()``.
Setting a default behaviour for skipping properties with empty values - `SkipWhenEmpty`_ annotation
---------------------------------------------------------------------------------------------------
To avoid to specifying the annotation ``@skipWhenEmpty`` for each property
it is possible to enable this behaviour in configuration by calling ``enableSkipWhenEmpty``

Example using with the SerializerBuilder::

use JMS\Serializer\SerializationContext;

$serializer = JMS\Serializer\SerializerBuilder::create()
->setSerializationContextFactory(function () {

return SerializationContext::create()
->enableSkipWhenEmpty();

})
->build();

.. _SkipWhenEmpty: reference/annotations.html#skipwhenempty
16 changes: 16 additions & 0 deletions src/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use JMS\Serializer\Exclusion\DisjunctExclusionStrategy;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
use JMS\Serializer\Exclusion\SkipWhenEmptyExclusionStrategy;
use JMS\Serializer\Exclusion\VersionExclusionStrategy;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
Expand All @@ -18,6 +19,7 @@

abstract class Context
{
public const ATTR_SKIP_WHEN_EMPTY = 'default_skip_when_empty';
/**
* @var array
*/
Expand Down Expand Up @@ -83,6 +85,10 @@ public function initialize(string $format, VisitorInterface $visitor, GraphNavig
$this->addExclusionStrategy(new DepthExclusionStrategy());
}

if (!empty($this->attributes[self::ATTR_SKIP_WHEN_EMPTY])) {
$this->addExclusionStrategy(new SkipWhenEmptyExclusionStrategy());
}

$this->initialized = true;
}

Expand Down Expand Up @@ -204,6 +210,16 @@ public function enableMaxDepthChecks(): self
return $this;
}

/**
* @return $this
*/
public function enableSkipWhenEmpty(): self
{
$this->attributes[self::ATTR_SKIP_WHEN_EMPTY] = true;

return $this;
}

public function getFormat(): string
{
return $this->format;
Expand Down
25 changes: 25 additions & 0 deletions src/Exclusion/SkipWhenEmptyExclusionStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Exclusion;

use JMS\Serializer\Context;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;

class SkipWhenEmptyExclusionStrategy implements ExclusionStrategyInterface
{
public function shouldSkipClass(ClassMetadata $metadata, Context $context): bool
{
return false;
}

public function shouldSkipProperty(PropertyMetadata $property, Context $context): bool
{
return $property->skipWhenEmpty
|| ($context->hasAttribute(Context::ATTR_SKIP_WHEN_EMPTY)
&& $context->getAttribute(Context::ATTR_SKIP_WHEN_EMPTY)
);
}
}
26 changes: 26 additions & 0 deletions tests/Fixtures/ObjectWithEmptyArrayAndHashNotAnnotated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation as Serializer;

class ObjectWithEmptyArrayAndHashNotAnnotated
{
/**
* @Serializer\Type("array<string,string>")
*/
private $hash = [];
/**
* @Serializer\Type("array<string>")
*/
private $array = [];

private $object = [];

public function __construct()
{
$this->object = new InlineChildEmpty();
}
}
20 changes: 20 additions & 0 deletions tests/Serializer/BaseSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,26 @@ public function testMaxDepthWithSkippableObject()
self::assertEquals($this->getContent('maxdepth_skippabe_object'), $serialized);
}

public function testSkipeWhenEmptyByDefaultEnabled()
{
$data = new Gh236Foo();

$context = SerializationContext::create()->enableSkipWhenEmpty();
$serialized = $this->serialize($data, $context);

self::assertEquals($this->getContent('default_skip_when_empty_enabled_object'), $serialized);
}

public function testSkipWhenEmptyByDefaultDisabled()
{
$data = new Gh236Foo();

$context = SerializationContext::create();
$serialized = $this->serialize($data, $context);

self::assertEquals($this->getContent('default_skip_when_empty_disabled_object'), $serialized);
}

public function testDeserializingIntoExistingObject()
{
if (!$this->hasDeserializer()) {
Expand Down
36 changes: 36 additions & 0 deletions tests/Serializer/JsonSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use JMS\Serializer\Tests\Fixtures\AuthorList;
use JMS\Serializer\Tests\Fixtures\FirstClassMapCollection;
use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyArrayAndHash;
use JMS\Serializer\Tests\Fixtures\ObjectWithEmptyArrayAndHashNotAnnotated;
use JMS\Serializer\Tests\Fixtures\ObjectWithInlineArray;
use JMS\Serializer\Tests\Fixtures\Tag;
use JMS\Serializer\Visitor\Factory\JsonSerializationVisitorFactory;
Expand Down Expand Up @@ -111,6 +112,8 @@ protected function getContent($key)
$outputs['author_expression'] = '{"my_first_name":"Ruud","last_name":"Kamphuis","id":123}';
$outputs['author_expression_context'] = '{"first_name":"Ruud","direction":1,"name":"name"}';
$outputs['maxdepth_skippabe_object'] = '{"a":{"xxx":"yyy"}}';
$outputs['default_skip_when_empty_enabled_object'] = '{}';
$outputs['default_skip_when_empty_disabled_object'] = '{"a":{"xxx":"yyy","inner":{"xxx":"yyy"}}}';
$outputs['array_objects_nullable'] = '[]';
$outputs['type_casting'] = '{"as_string":"8"}';
$outputs['authors_inline'] = '[{"full_name":"foo"},{"full_name":"bar"}]';
Expand Down Expand Up @@ -146,6 +149,25 @@ public function testSkipEmptyArrayAndHash()
self::assertEquals('{}', $this->serialize($object));
}

/**
* @param bool $flagEnabled
* @param string $expectedSerializedValue
*
* @dataProvider getSkipEmptyArrayAndHashValues
*/
public function testSkipEmptyArrayAndHash1(bool $flagEnabled, string $expectedSerializedValue)
{
$object = new ObjectWithEmptyArrayAndHashNotAnnotated();

$context = SerializationContext::create();

if ($flagEnabled) {
$context->enableSkipWhenEmpty();
}

self::assertEquals($expectedSerializedValue, $this->serialize($object, $context));
}

public function getFirstClassMapCollectionsValues()
{
return [
Expand Down Expand Up @@ -428,6 +450,20 @@ public function testTypeHintedArrayAndStdClassSerialization(array $array, $expec
self::assertEquals($expected, $this->serialize($array, $context));
}

public function getSkipEmptyArrayAndHashValues()
{
return [
'default skip_when_empty disabled' => [
false,
'expected' => '{"hash":{},"array":[],"object":{}}',
],
'default skip_when_empty enabled' => [
true,
'expected' => '{}',
],
];
}

protected function getFormat()
{
return 'json';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<result>
<a>
<xxx><![CDATA[yyy]]></xxx>
<inner>
<xxx><![CDATA[yyy]]></xxx>
</inner>
</a>
</result>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<result/>

0 comments on commit 25ca44d

Please sign in to comment.