diff --git a/README.md b/README.md index 638ca15..1046462 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,13 @@ Adds operation to `JsonPatch`. #### `apply` Applies patch to `JSON`-decoded data. +#### `setFlags` +Alters default behavior. + +Available flag: + +* `JsonPatch::STRICT_MODE` Disallow converting empty array to object for key creation. + ### `JsonPointer` #### `escapeSegment` diff --git a/src/JsonPatch.php b/src/JsonPatch.php index 0f273e2..67d53b9 100644 --- a/src/JsonPatch.php +++ b/src/JsonPatch.php @@ -19,6 +19,24 @@ */ class JsonPatch implements \JsonSerializable { + /** + * Disallow converting empty array to object for key creation + * @see JsonPointer::STRICT_MODE + */ + const STRICT_MODE = 2; + + private $flags = 0; + + /** + * @param int $options + * @return $this + */ + public function setFlags($options) + { + $this->flags = $options; + return $this; + } + /** @var OpPath[] */ private $operations = array(); @@ -118,18 +136,18 @@ public function apply(&$original, $stopOnError = true) $pathItems = JsonPointer::splitPath($operation->path); switch (true) { case $operation instanceof Add: - JsonPointer::add($original, $pathItems, $operation->value, false); + JsonPointer::add($original, $pathItems, $operation->value, $this->flags); break; case $operation instanceof Copy: $fromItems = JsonPointer::splitPath($operation->from); $value = JsonPointer::get($original, $fromItems); - JsonPointer::add($original, $pathItems, $value, false); + JsonPointer::add($original, $pathItems, $value, $this->flags); break; case $operation instanceof Move: $fromItems = JsonPointer::splitPath($operation->from); $value = JsonPointer::get($original, $fromItems); JsonPointer::remove($original, $fromItems); - JsonPointer::add($original, $pathItems, $value, false); + JsonPointer::add($original, $pathItems, $value, $this->flags); break; case $operation instanceof Remove: JsonPointer::remove($original, $pathItems); @@ -137,7 +155,7 @@ public function apply(&$original, $stopOnError = true) case $operation instanceof Replace: JsonPointer::get($original, $pathItems); JsonPointer::remove($original, $pathItems); - JsonPointer::add($original, $pathItems, $operation->value, false); + JsonPointer::add($original, $pathItems, $operation->value, $this->flags); break; case $operation instanceof Test: $value = JsonPointer::get($original, $pathItems); diff --git a/src/JsonPointer.php b/src/JsonPointer.php index c2ee426..6ba10e7 100644 --- a/src/JsonPointer.php +++ b/src/JsonPointer.php @@ -5,6 +5,16 @@ class JsonPointer { + /** + * Create intermediate keys if they don't exist + */ + const RECURSIVE_KEY_CREATION = 1; + + /** + * Disallow converting empty array to object for key creation + */ + const STRICT_MODE = 2; + /** * @param string $key * @param bool $isURIFragmentId @@ -76,10 +86,10 @@ private static function splitPathJsonString(array $pathItems) * @param mixed $holder * @param string[] $pathItems * @param mixed $value - * @param bool $recursively + * @param int $flags * @throws Exception */ - public static function add(&$holder, $pathItems, $value, $recursively = true) + public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIVE_KEY_CREATION) { $ref = &$holder; while (null !== $key = array_shift($pathItems)) { @@ -89,7 +99,7 @@ public static function add(&$holder, $pathItems, $value, $recursively = true) Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED); } - if ($recursively) { + if ($flags & self::RECURSIVE_KEY_CREATION) { $ref = &$ref->$key; } else { if (!isset($ref->$key) && count($pathItems)) { @@ -102,17 +112,17 @@ public static function add(&$holder, $pathItems, $value, $recursively = true) $intKey = filter_var($key, FILTER_VALIDATE_INT); if ($ref === null && (false === $intKey || $intKey !== 0)) { $key = (string)$key; - if ($recursively) { + if ($flags & self::RECURSIVE_KEY_CREATION) { $ref = new \stdClass(); $ref = &$ref->{$key}; } else { throw new Exception('Non-existent path item: ' . $key); } - } elseif ([] === $ref && false === $intKey && '-' !== $key) { + } elseif ([] === $ref && 0 === ($flags & self::STRICT_MODE) && false === $intKey && '-' !== $key) { $ref = new \stdClass(); $ref = &$ref->{$key}; } else { - if ($recursively && $ref === null) $ref = array(); + if ($flags & self::RECURSIVE_KEY_CREATION && $ref === null) $ref = array(); if ('-' === $key) { $ref = &$ref[]; } else { @@ -122,7 +132,7 @@ public static function add(&$holder, $pathItems, $value, $recursively = true) if (false === $intKey) { throw new Exception('Invalid key for array operation'); } - if ($intKey > count($ref) && !$recursively) { + if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) { throw new Exception('Index is greater than number of items in array'); } elseif ($intKey < 0) { throw new Exception('Negative index'); diff --git a/tests/src/Issues/Issue9Test.php b/tests/src/Issues/Issue9Test.php index e2f0f60..6dbfd7b 100644 --- a/tests/src/Issues/Issue9Test.php +++ b/tests/src/Issues/Issue9Test.php @@ -2,7 +2,9 @@ namespace Swaggest\JsonDiff\Tests\Issues; +use Swaggest\JsonDiff\Exception; use Swaggest\JsonDiff\JsonDiff; +use Swaggest\JsonDiff\JsonPatch; /** * @see https://github.com/swaggest/json-diff/issues/9 @@ -18,5 +20,9 @@ public function testPatchApply() $this->assertNotEquals($new, $old); $patch->apply($old); $this->assertEquals($new, $old); + + $old = json_decode(json_encode(["emptyObject" => []])); + $this->setExpectedException(get_class(new Exception()), 'Invalid key for array operation'); + $patch->setFlags(JsonPatch::STRICT_MODE)->apply($old); } }