Skip to content

Commit

Permalink
Allow deserializing of null
Browse files Browse the repository at this point in the history
  • Loading branch information
kunicmarko20 authored and goetas committed May 3, 2019
1 parent f597012 commit edb07a6
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 5 deletions.
24 changes: 24 additions & 0 deletions src/DeserializationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ class DeserializationContext extends Context
*/
private $depth = 0;

/**
* @var bool
*/
private $deserializeNull = false;

public static function create(): self
{
return new self();
Expand All @@ -33,6 +38,25 @@ public function increaseDepth(): void
$this->depth += 1;
}

/**
* Set if NULLs should be deserialized (TRUE) ot not (FALSE)
*/
public function setDeserializeNull(bool $bool): self
{
$this->deserializeNull = $bool;

return $this;
}

/**
* Returns TRUE when NULLs should be deserialized
* Returns FALSE when NULLs should not be deserialized
*/
public function shouldDeserializeNull(): bool
{
return $this->deserializeNull;
}

public function decreaseDepth(): void
{
if ($this->depth <= 0) {
Expand Down
24 changes: 22 additions & 2 deletions src/GraphNavigator/DeserializationGraphNavigator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use JMS\Serializer\Accessor\AccessorStrategyInterface;
use JMS\Serializer\Construction\ObjectConstructorInterface;
use JMS\Serializer\Context;
use JMS\Serializer\DeserializationContext;
use JMS\Serializer\EventDispatcher\EventDispatcher;
use JMS\Serializer\EventDispatcher\EventDispatcherInterface;
Expand All @@ -23,6 +24,7 @@
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\NullAwareVisitorInterface;
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
use JMS\Serializer\VisitorInterface;
use Metadata\MetadataFactoryInterface;

/**
Expand Down Expand Up @@ -74,6 +76,11 @@ final class DeserializationGraphNavigator extends GraphNavigator implements Grap
*/
private $accessor;

/**
* @var bool
*/
private $shouldDeserializeNull;

public function __construct(
MetadataFactoryInterface $metadataFactory,
HandlerRegistryInterface $handlerRegistry,
Expand All @@ -92,6 +99,12 @@ public function __construct(
}
}

public function initialize(VisitorInterface $visitor, Context $context): void
{
parent::initialize($visitor, $context);
$this->shouldDeserializeNull = $context->shouldDeserializeNull();
}

/**
* Called for each node of the graph that is being traversed.
*
Expand All @@ -109,7 +122,11 @@ public function accept($data, ?array $type = null)
}
// Sometimes data can convey null but is not of a null type.
// Visitors can have the power to add this custom null evaluation
if ($this->visitor instanceof NullAwareVisitorInterface && true === $this->visitor->isNull($data)) {
// If null is explicitly allowed we should skip this
if ($this->visitor instanceof NullAwareVisitorInterface
&& true === $this->visitor->isNull($data)
&& (!empty($type['nullable']) || false === $this->shouldDeserializeNull)
) {
$type = ['name' => 'NULL', 'params' => []];
}

Expand Down Expand Up @@ -201,7 +218,10 @@ public function accept($data, ?array $type = null)
$this->context->pushPropertyMetadata($propertyMetadata);
try {
$v = $this->visitor->visitProperty($propertyMetadata, $data);
$this->accessor->setValue($object, $v, $propertyMetadata, $this->context);

if (null !== $v || true === $this->shouldDeserializeNull) {
$this->accessor->setValue($object, $v, $propertyMetadata, $this->context);
}
} catch (NotAcceptableException $e) {
}
$this->context->popPropertyMetadata();
Expand Down
10 changes: 9 additions & 1 deletion src/JsonDeserializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Visitor\DeserializationVisitorInterface;

final class JsonDeserializationVisitor extends AbstractVisitor implements DeserializationVisitorInterface
final class JsonDeserializationVisitor extends AbstractVisitor implements NullAwareVisitorInterface, DeserializationVisitorInterface
{
/**
* @var int
Expand Down Expand Up @@ -244,4 +244,12 @@ public function prepare($str)
throw new RuntimeException('Could not decode JSON.');
}
}

/**
* {@inheritdoc}
*/
public function isNull($value): bool
{
return null === $value;
}
}
24 changes: 24 additions & 0 deletions tests/Fixtures/ObjectWithNullObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation\Type;

class ObjectWithNullObject
{
/**
* @var null
* @Type("NullObject")
*/
private $nullProperty;

/**
* @return null
*/
public function getNullProperty()
{
return $this->nullProperty;
}
}
59 changes: 58 additions & 1 deletion tests/Serializer/BaseSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
use JMS\Serializer\Tests\Fixtures\ObjectWithIntListAndIntMap;
use JMS\Serializer\Tests\Fixtures\ObjectWithIterator;
use JMS\Serializer\Tests\Fixtures\ObjectWithLifecycleCallbacks;
use JMS\Serializer\Tests\Fixtures\ObjectWithNullObject;
use JMS\Serializer\Tests\Fixtures\ObjectWithNullProperty;
use JMS\Serializer\Tests\Fixtures\ObjectWithToString;
use JMS\Serializer\Tests\Fixtures\ObjectWithTypedArraySetter;
Expand Down Expand Up @@ -209,6 +210,25 @@ public function testDeserializeNullObject()
self::assertNull($dObj->getNullProperty());
}

public function testDeserializeNullObjectWithHandler()
{
if (!$this->hasDeserializer()) {
$this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat()));
}
$ctx = DeserializationContext::create()
->setDeserializeNull(true);

/** @var ObjectWithNullObject $dObj */
$dObj = $this->serializer->deserialize(
$this->getContent('simple_object_nullable'),
ObjectWithNullObject::class,
$this->getFormat(),
$ctx
);

self::assertSame('nullObject', $dObj->getNullProperty());
}

/**
* @expectedException \JMS\Serializer\Exception\NotAcceptableException
* @dataProvider getTypes
Expand Down Expand Up @@ -746,6 +766,36 @@ public function testDeserializingNull()
if ($this->hasDeserializer()) {
$deserialized = $this->deserialize($this->getContent('blog_post_unauthored'), get_class($post), DeserializationContext::create());

self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM));
self::assertAttributeEquals('This is a nice title.', 'title', $deserialized);
self::assertAttributeSame(false, 'published', $deserialized);
self::assertAttributeSame(false, 'reviewed', $deserialized);
self::assertAttributeEquals(new ArrayCollection(), 'comments', $deserialized);
self::assertAttributeEquals(null, 'author', $deserialized);
}
}

public function testDeserializingNullAllowed()
{
$objectConstructor = new InitializedBlogPostConstructor();

$builder = SerializerBuilder::create();
$builder->setObjectConstructor($objectConstructor);
$this->serializer = $builder->build();

$post = new BlogPost('This is a nice title.', $author = new Author('Foo Bar'), new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), new Publisher('Bar Foo'));

$this->setField($post, 'author', null);
$this->setField($post, 'publisher', null);

self::assertEquals($this->getContent('blog_post_unauthored'), $this->serialize($post, SerializationContext::create()->setSerializeNull(true)));

if ($this->hasDeserializer()) {
$ctx = DeserializationContext::create();
$ctx->setDeserializeNull(true);

$deserialized = $this->deserialize($this->getContent('blog_post_unauthored'), get_class($post), $ctx);

self::assertEquals('2011-07-30T00:00:00+00:00', $this->getField($deserialized, 'createdAt')->format(\DateTime::ATOM));
self::assertAttributeEquals('This is a nice title.', 'title', $deserialized);
self::assertAttributeSame(false, 'published', $deserialized);
Expand Down Expand Up @@ -1668,7 +1718,14 @@ static function (DeserializationVisitorInterface $visitor, $data, $type, Context
return $list;
}
);

$this->handlerRegistry->registerHandler(
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
'NullObject',
$this->getFormat(),
static function (DeserializationVisitorInterface $visitor, $data, $type, Context $context) {
return 'nullObject';
}
);
$this->dispatcher = new EventDispatcher();
$this->dispatcher->addSubscriber(new DoctrineProxySubscriber());

Expand Down
1 change: 0 additions & 1 deletion tests/Serializer/GraphNavigatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ protected function setUp()
->enableOriginalConstructor()
->setMethodsExcept(['getExclusionStrategy'])
->getMock();

$this->deserializationContext = $this->getMockBuilder(DeserializationContext::class)
->enableOriginalConstructor()
->setMethodsExcept(['getExclusionStrategy'])
Expand Down
5 changes: 5 additions & 0 deletions tests/Serializer/XmlSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,11 @@ public function testDeserializingNull()
$this->markTestSkipped('Not supported in XML.');
}

public function testDeserializingNullAllowed()
{
$this->markTestSkipped('Not supported in XML.');
}

public function testObjectWithXmlNamespaces()
{
$object = new ObjectWithXmlNamespaces('This is a nice title.', 'Foo Bar', new \DateTime('2011-07-30 00:00', new \DateTimeZone('UTC')), 'en');
Expand Down

0 comments on commit edb07a6

Please sign in to comment.