Skip to content

Commit

Permalink
Reject NULL values in arrays unless explicitly allowed
Browse files Browse the repository at this point in the history
BC break!

The `$bStrictNullTypes` configuration option did not cover
NULL values in arrays, which is unexpected.

This patch adds a new config option `$bStrictNullTypesInArrays`
in JsonMapper, which defaults to `true` and rejects NULL
values in arrays unless explicitly allowed by a nullable type declaration:
`array[?int]`.

This is a backwards compatibility break.
The old behavior can be restored by setting the new configuration
option to `false`.

Resolves: #233
Related: #211
  • Loading branch information
cweiske committed Jul 20, 2024
1 parent cf85ec1 commit 613b29b
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 4 deletions.
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
=======
Expand Down
29 changes: 27 additions & 2 deletions src/JsonMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
58 changes: 56 additions & 2 deletions tests/ArrayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 10 additions & 0 deletions tests/support/JsonMapperTest/Array.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 613b29b

Please sign in to comment.