Skip to content

Commit

Permalink
Fix xml deserialization when xsi:nil="true" is set
Browse files Browse the repository at this point in the history
Add xml deserialization null check based on xsi:nil
Expand visitor interface with a null data detect function
Add default implementation for null detection in abstract visitor
Add round-trip test for serializing ad de-serializing null object
  • Loading branch information
pbouwdewijn committed May 22, 2017
1 parent a11a2fb commit e4b5d82
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/AbstractVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,13 @@ protected function getElementType($typeArray)
}
}

/**
* @param mixed $data
*
* @return bool
*/
public function isNullData($data)
{
return $data === null;
}
}
3 changes: 2 additions & 1 deletion src/GraphNavigator.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,10 @@ public function accept($data, array $type = null, Context $context)

$type = array('name' => $typeName, 'params' => array());
}

// If the data is null, we have to force the type to null regardless of the input in order to
// guarantee correct handling of null values, and not have any internal auto-casting behavior.
else if ($context instanceof SerializationContext && null === $data) {
if ($visitor instanceof VisitorInterface && true === $visitor->isNullData($data)) {
$type = array('name' => 'NULL', 'params' => array());
}

Expand Down
10 changes: 10 additions & 0 deletions src/VisitorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,14 @@ public function getNavigator();
* @return object|array|scalar
*/
public function getResult();

/**
* Determine if the value evaluates to null.
* Used by the navigator to determine the correct data type.
*
* @param mixed $data
*
* @return bool
*/
public function isNullData($data);
}
22 changes: 22 additions & 0 deletions src/XmlDeserializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,26 @@ private function getDomDocumentTypeEntitySubset($data)

return $internalSubset;
}

/**
* Consider xml element value null if the xsi:nil attribute is set and therefore can't have a value
* @see https://www.w3.org/TR/xmlschema-1/#xsi_nil
*
* @param $data
*
* @return bool
*/
public function isNullData($data)
{
if ($data instanceof \SimpleXMLElement) {
$xsiAttributes = $data->attributes('http://www.w3.org/2001/XMLSchema-instance');

//We have to keep the isset quiet, some tests give error: `Node no longer exists`; even though it evaluates to false
if (@isset($xsiAttributes['nil']) && (string) $xsiAttributes['nil'] === 'true') {
return true;
}
}

return $data === null;
}
}
14 changes: 14 additions & 0 deletions tests/Fixtures/ObjectWithNullProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,21 @@

namespace JMS\Serializer\Tests\Fixtures;

use JMS\Serializer\Annotation\Type;

class ObjectWithNullProperty extends SimpleObject
{
/**
* @var null
* @Type("string")
*/
private $nullProperty = null;

/**
* @return null
*/
public function getNullProperty()
{
return $this->nullProperty;
}
}
32 changes: 32 additions & 0 deletions tests/Serializer/BaseSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,25 @@ public function testSerializeNullObject()
);
}

public function testDeserializeNullObject()
{
if (!$this->hasDeserializer()) {
$this->markTestSkipped(sprintf('No deserializer available for format `%s`', $this->getFormat()));
}

$obj = new ObjectWithNullProperty('foo', 'bar');

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

$this->assertEquals($obj, $dObj);
$this->assertNull($dObj->getNullProperty());
}

/**
* @dataProvider getTypes
*/
Expand Down Expand Up @@ -1345,6 +1364,19 @@ public function testObjectWithNullableArrays()
$this->assertEquals($this->getContent('nullable_arrays'), $this->serializer->serialize($object, $this->getFormat()));
}

public function testIsNullDataWithNull()
{
/** @var VisitorInterface $visitor */
foreach ($this->serializationVisitors as $visitor) {
$this->assertTrue($visitor->isNullData(null));
}

/** @var VisitorInterface $visitor */
foreach ($this->deserializationVisitors as $visitor) {
$this->assertTrue($visitor->isNullData(null));
}
}

abstract protected function getContent($key);

abstract protected function getFormat();
Expand Down
11 changes: 11 additions & 0 deletions tests/Serializer/XmlSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use JMS\Serializer\Handler\HandlerRegistry;
use JMS\Serializer\Metadata\StaticPropertyMetadata;
use JMS\Serializer\Naming\CamelCaseNamingStrategy;
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
use JMS\Serializer\Naming\SerializedNameAnnotationStrategy;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\Serializer;
Expand All @@ -52,6 +53,7 @@
use JMS\Serializer\Tests\Fixtures\SimpleClassObject;
use JMS\Serializer\Tests\Fixtures\SimpleObject;
use JMS\Serializer\Tests\Fixtures\SimpleSubClassObject;
use JMS\Serializer\XmlDeserializationVisitor;
use JMS\Serializer\XmlSerializationVisitor;
use PhpCollection\Map;

Expand Down Expand Up @@ -488,6 +490,15 @@ public function testDeserializeEmptyString()
$this->deserialize('', 'stdClass');
}

public function testIsNullDataWithXSINilLabeledElement()
{
$namingStrategy = $this->getMockBuilder(PropertyNamingStrategyInterface::class)->getMock();
$visitor = new XmlDeserializationVisitor($namingStrategy);
$element = simplexml_load_string('<empty xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>');

$this->assertTrue($visitor->isNullData($element));
}

private function xpathFirstToString(\SimpleXMLElement $xml, $xpath)
{
$nodes = $xml->xpath($xpath);
Expand Down

0 comments on commit e4b5d82

Please sign in to comment.