diff --git a/README.rst b/README.rst index bda4b5daf..142f4c60a 100644 --- a/README.rst +++ b/README.rst @@ -336,6 +336,15 @@ to make all your type definitions nullable, set: $jm->bStrictNullTypes = false; +Since version 5.0.0, ``null`` values in arrays lead to a ``JsonMapper_Exception`` +unless the type is nullable - e.g. ``array[?string]`` or ``array[string|null]``. + +To get the previous behavior back (allowing nulls even when not declared so) set: + +.. code:: php + + $jm->bStrictNullTypesInArrays = false; + Logging ======= diff --git a/src/JsonMapper.php b/src/JsonMapper.php index 5496d2878..2d73f9297 100644 --- a/src/JsonMapper.php +++ b/src/JsonMapper.php @@ -70,10 +70,18 @@ class JsonMapper * Throw an exception, if null value is found * but the type of attribute does not allow nulls. * - * @var bool + * @var boolean */ public $bStrictNullTypes = true; + /** + * Throw an exception if null value is found in an array + * but the type of attribute does not allow nulls. + * + * @var boolean + */ + public $bStrictNullTypesInArrays = true; + /** * Allow mapping of private and protected properties. * @@ -277,7 +285,8 @@ public function map($json, $object) 'Empty type at property "' . $strClassName . '::$' . $key . '"' ); - } else if (strpos($type, '|')) { + + } else if (strpos(str_replace('|null', '', $type), '|')) { throw new JsonMapper_Exception( 'Cannot decide which of the union types shall be used: ' . $type @@ -316,9 +325,14 @@ public function map($json, $object) ); } + $subtypeNullable = $this->isNullable($subtype); $cleanSubtype = $this->removeNullable($subtype); $subtype = $this->getFullNamespace($cleanSubtype, $strNs); + if ($subtypeNullable) { + $subtype = '?' . $subtype; + } $child = $this->mapArray($jvalue, $array, $subtype, $key); + } else if ($this->isFlatType(gettype($jvalue))) { //use constructor parameter if we have a class // but only a flat type (i.e. string, int) @@ -447,8 +461,19 @@ protected function removeUndefinedAttributes($object, $providedProperties) */ public function mapArray($json, $array, $class = null, $parent_key = '') { + $isNullable = $this->isNullable($class); + $class = $this->removeNullable($class); $originalClass = $class; + foreach ($json as $key => $jvalue) { + if ($jvalue === null && !$isNullable && $this->bStrictNullTypesInArrays) { + throw new JsonMapper_Exception( + 'JSON property' + . ' "' . ($parent_key ? $parent_key : '?') . '[' . $key . ']"' + . ' must not be NULL' + ); + } + $class = $this->getMappedType($originalClass, $jvalue); if ($class === null) { $array[$key] = $jvalue; diff --git a/tests/ArrayTest.php b/tests/ArrayTest.php index 1daee0645..c0511bc41 100644 --- a/tests/ArrayTest.php +++ b/tests/ArrayTest.php @@ -48,6 +48,7 @@ public function testMapTypedArray() public function testMapTypedSimpleArray() { $jm = new JsonMapper(); + $jm->bStrictNullTypesInArrays = false; $sn = $jm->map( json_decode('{"typedSimpleArray":["2014-01-02",null,"2014-05-07"]}'), new JsonMapperTest_Array() @@ -138,10 +139,63 @@ public function testStrArrayV2() $this->assertSame(['str', '', '2.048'], $sn->strArrayV2); } - public function testNullArrayValue() + /** + * Test for an array of strings-or-null values - "@var array[string|null]" + */ + public function testStrMaybeNullArray() + { + $jm = new JsonMapper(); + $this->assertTrue($jm->bStrictNullTypesInArrays); + $sn = $jm->map( + json_decode('{"strMaybeNullArray":["str",null,2.048]}'), + new JsonMapperTest_Array() + ); + $this->assertSame(['str', null, '2.048'], $sn->strMaybeNullArray); + } + + /** + * Test for an array of strings-or-null values - "@var array[?string]" + * Alternative syntax + */ + public function testStrMaybeNullArrayV2() + { + $jm = new JsonMapper(); + $this->assertTrue($jm->bStrictNullTypesInArrays); + $sn = $jm->map( + json_decode('{"strMaybeNullArrayV2":["str",null,2.048]}'), + new JsonMapperTest_Array() + ); + $this->assertSame(['str', null, '2.048'], $sn->strMaybeNullArrayV2); + } + + public function testNullArrayValueStrict() { + $this->expectException(JsonMapper_Exception::class); + $this->expectExceptionMessage('JSON property "strArray[1]" must not be NULL'); + $jm = new JsonMapper(); - $jm->bStrictNullTypes = true; + $this->assertTrue( + $jm->bStrictNullTypes, + 'Default value for bStrictNullTypes is wrong' + ); + $this->assertTrue( + $jm->bStrictNullTypesInArrays, + 'Default value for bStrictNullTypesInArrays is wrong' + ); + $sn = $jm->map( + json_decode('{"strArray":["a",null,"c"]}'), + new JsonMapperTest_Array() + ); + } + + public function testNullArrayValueNonStrict() + { + $jm = new JsonMapper(); + $this->assertTrue( + $jm->bStrictNullTypes, + 'Default value for bStrictNullTypes is wrong' + ); + $jm->bStrictNullTypesInArrays = false; $sn = $jm->map( json_decode('{"strArray":["a",null,"c"]}'), new JsonMapperTest_Array() diff --git a/tests/support/JsonMapperTest/Array.php b/tests/support/JsonMapperTest/Array.php index 90c6d50f9..b1ab4fdc6 100644 --- a/tests/support/JsonMapperTest/Array.php +++ b/tests/support/JsonMapperTest/Array.php @@ -35,6 +35,16 @@ class JsonMapperTest_Array */ public $strArrayV2; + /** + * @var array[string|null] + */ + public $strMaybeNullArray; + + /** + * @var array[?string] + */ + public $strMaybeNullArrayV2; + /** * @var JsonMapperTest_Simple[] * @see http://phpdoc.org/docs/latest/references/phpdoc/types.html#arrays