From c7d15544c5c459adc4507f0d19fb26a4afcab3ed Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 3 May 2025 15:13:38 +0200 Subject: [PATCH 1/3] Handle empty objects --- .../php/text/json/patch/ArrayEnd.class.php | 5 +++- .../text/json/patch/ObjectMember.class.php | 5 +++- .../json/patch/unittest/ChangesTest.class.php | 2 +- .../json/patch/unittest/PointerTest.class.php | 28 +++++++++++++++++-- .../unittest/RemoveOperationTest.class.php | 14 ++++++++-- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/main/php/text/json/patch/ArrayEnd.class.php b/src/main/php/text/json/patch/ArrayEnd.class.php index 70e4f36..dfaa480 100755 --- a/src/main/php/text/json/patch/ArrayEnd.class.php +++ b/src/main/php/text/json/patch/ArrayEnd.class.php @@ -39,6 +39,7 @@ public function remove() { if ($this->parent->exists) { if (array_key_exists('-', $this->parent->reference)) { unset($this->parent->reference['-']); + empty($this->parent->reference) && $this->parent->reference= (object)[]; return Applied::$CLEANLY; } else if (0 === key($this->parent->reference)) { array_pop($this->parent->reference); @@ -56,7 +57,9 @@ public function remove() { */ public function add($value) { if ($this->parent->exists) { - if (empty($this->parent->reference) || 0 === key($this->parent->reference)) { + if (is_object($this->parent->reference)) { + $this->parent->reference= ['-' => $value]; + } else if (empty($this->parent->reference) || 0 === key($this->parent->reference)) { $this->parent->reference[]= $value; return Applied::$CLEANLY; } else { diff --git a/src/main/php/text/json/patch/ObjectMember.class.php b/src/main/php/text/json/patch/ObjectMember.class.php index 295caa2..5ff104c 100755 --- a/src/main/php/text/json/patch/ObjectMember.class.php +++ b/src/main/php/text/json/patch/ObjectMember.class.php @@ -44,6 +44,7 @@ public function modify($value) { public function remove() { if ($this->exists) { unset($this->parent->reference[$this->name]); + empty($this->parent->reference) && $this->parent->reference= (object)[]; return Applied::$CLEANLY; } else { return new PathDoesNotExist($this->path()); @@ -58,7 +59,9 @@ public function remove() { */ public function add($value) { if ($this->parent->exists) { - if (0 === key($this->parent->reference)) { + if (is_object($this->parent->reference)) { + $this->parent->reference= []; + } else if (0 === key($this->parent->reference)) { return new TypeConflict('Object operation on array target'); } diff --git a/src/test/php/text/json/patch/unittest/ChangesTest.class.php b/src/test/php/text/json/patch/unittest/ChangesTest.class.php index 5c2f38e..81a6fd9 100755 --- a/src/test/php/text/json/patch/unittest/ChangesTest.class.php +++ b/src/test/php/text/json/patch/unittest/ChangesTest.class.php @@ -1,8 +1,8 @@ 'original']; (new Pointer('/text'))->resolve($value)->remove(); - Assert::equals([], $value); + Assert::equals((object)[], $value); } #[Test] @@ -192,6 +192,20 @@ public function add_object_member() { Assert::equals(['a' => 'original', 'b' => 'added'], $value); } + #[Test] + public function add_object_member_to_empty() { + $value= (object)[]; + (new Pointer('/a'))->resolve($value)->add('added'); + Assert::equals(['a' => 'added'], $value); + } + + #[Test] + public function dash_for_empty_object() { + $value= (object)[]; + (new Pointer('/-'))->resolve($value)->add('added'); + Assert::equals(['-' => 'added'], $value); + } + #[Test] public function cannot_add_object_member_to_array() { $value= [1, 2, 3]; @@ -209,4 +223,14 @@ public function cannot_add_to_nonexistant_object() { $value= []; Assert::instance('text.json.patch.PathDoesNotExist', (new Pointer('/non-existant/member'))->resolve($value)->add('test')); } + + #[Test] + public function remove_then_add_back_object_member() { + $value= ['text' => 'original']; + $ptr= new Pointer('/text'); + $ptr->resolve($value)->remove(); + $ptr->resolve($value)->add('changed'); + + Assert::equals(['text' => 'changed'], $value); + } } \ No newline at end of file diff --git a/src/test/php/text/json/patch/unittest/RemoveOperationTest.class.php b/src/test/php/text/json/patch/unittest/RemoveOperationTest.class.php index 0ee1795..243293e 100755 --- a/src/test/php/text/json/patch/unittest/RemoveOperationTest.class.php +++ b/src/test/php/text/json/patch/unittest/RemoveOperationTest.class.php @@ -1,8 +1,7 @@ 'bar'], $value); } + #[Test] + public function removing_last_object_member() { + $operation= new RemoveOperation('/baz'); + + $value= ['baz' => 'qux']; + Assert::equals(Applied::$CLEANLY, $operation->applyTo($value)); + Assert::equals((object)[], $value); + } + #[Test] public function removing_an_array_element() { $operation= new RemoveOperation('/foo/1'); @@ -54,7 +62,7 @@ public function dash_has_no_special_meaning_for_non_arrays() { $value= ['-' => 4]; Assert::equals(Applied::$CLEANLY, $operation->applyTo($value)); - Assert::equals([], $value); + Assert::equals((object)[], $value); } #[Test] From fc82169868ffa24ce5ba2d79fdfff0805d44b71b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 3 May 2025 15:33:32 +0200 Subject: [PATCH 2/3] Handle object inputs --- .../php/text/json/patch/ArrayEnd.class.php | 2 +- .../text/json/patch/ObjectMember.class.php | 2 + .../json/patch/unittest/PointerTest.class.php | 41 +++++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/main/php/text/json/patch/ArrayEnd.class.php b/src/main/php/text/json/patch/ArrayEnd.class.php index dfaa480..8f1d88e 100755 --- a/src/main/php/text/json/patch/ArrayEnd.class.php +++ b/src/main/php/text/json/patch/ArrayEnd.class.php @@ -58,7 +58,7 @@ public function remove() { public function add($value) { if ($this->parent->exists) { if (is_object($this->parent->reference)) { - $this->parent->reference= ['-' => $value]; + $this->parent->reference= ['-' => $value] + (array)$this->parent->reference; } else if (empty($this->parent->reference) || 0 === key($this->parent->reference)) { $this->parent->reference[]= $value; return Applied::$CLEANLY; diff --git a/src/main/php/text/json/patch/ObjectMember.class.php b/src/main/php/text/json/patch/ObjectMember.class.php index 5ff104c..1d0d56d 100755 --- a/src/main/php/text/json/patch/ObjectMember.class.php +++ b/src/main/php/text/json/patch/ObjectMember.class.php @@ -11,6 +11,8 @@ class ObjectMember extends Address { */ public function __construct($name, parent $parent) { $this->name= $name; + is_object($parent->reference) && $parent->reference= (array)$parent->reference; + if (is_array($parent->reference) && array_key_exists($this->name, $parent->reference)) { parent::__construct($parent->reference[$this->name], $parent); } else { diff --git a/src/test/php/text/json/patch/unittest/PointerTest.class.php b/src/test/php/text/json/patch/unittest/PointerTest.class.php index 6e4b661..e3058a4 100755 --- a/src/test/php/text/json/patch/unittest/PointerTest.class.php +++ b/src/test/php/text/json/patch/unittest/PointerTest.class.php @@ -115,12 +115,19 @@ public function modify_array_index() { } #[Test] - public function modify_object_member() { + public function modify_object_member_on_array() { $value= ['text' => 'original']; (new Pointer('/text'))->resolve($value)->modify('modified'); Assert::equals(['text' => 'modified'], $value); } + #[Test] + public function modify_object_member_on_object() { + $value= (object)['text' => 'original']; + (new Pointer('/text'))->resolve($value)->modify('modified'); + Assert::equals(['text' => 'modified'], $value); + } + #[Test] public function modify_non_existant_array_index() { $value= []; @@ -147,12 +154,26 @@ public function remove_array_index() { } #[Test] - public function remove_object_member() { + public function remove_last_object_membery() { $value= ['text' => 'original']; (new Pointer('/text'))->resolve($value)->remove(); Assert::equals((object)[], $value); } + #[Test] + public function remove_object_member_from_array() { + $value= ['text' => 'original', 'key' => 'value']; + (new Pointer('/text'))->resolve($value)->remove(); + Assert::equals(['key' => 'value'], $value); + } + + #[Test] + public function remove_object_member_from_object() { + $value= (object)['text' => 'original', 'key' => 'value']; + (new Pointer('/text'))->resolve($value)->remove(); + Assert::equals(['key' => 'value'], $value); + } + #[Test] public function remove_non_existant_array_index() { $value= []; @@ -186,12 +207,19 @@ public function add_at_end_of_array() { } #[Test] - public function add_object_member() { + public function add_object_member_to_array() { $value= ['a' => 'original']; (new Pointer('/b'))->resolve($value)->add('added'); Assert::equals(['a' => 'original', 'b' => 'added'], $value); } + #[Test] + public function add_object_member_to_object() { + $value= (object)['a' => 'original']; + (new Pointer('/b'))->resolve($value)->add('added'); + Assert::equals(['a' => 'original', 'b' => 'added'], $value); + } + #[Test] public function add_object_member_to_empty() { $value= (object)[]; @@ -199,6 +227,13 @@ public function add_object_member_to_empty() { Assert::equals(['a' => 'added'], $value); } + #[Test] + public function dash_for_object() { + $value= (object)['key' => 'value']; + (new Pointer('/-'))->resolve($value)->add('added'); + Assert::equals(['key' => 'value', '-' => 'added'], $value); + } + #[Test] public function dash_for_empty_object() { $value= (object)[]; From ad88c21022517f2802b696e48e99fb1ad7ed099a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 3 May 2025 15:37:25 +0200 Subject: [PATCH 3/3] Handle objects in array index --- src/main/php/text/json/patch/ArrayIndex.class.php | 2 ++ .../php/text/json/patch/unittest/PointerTest.class.php | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/main/php/text/json/patch/ArrayIndex.class.php b/src/main/php/text/json/patch/ArrayIndex.class.php index f0f5cdb..9868c84 100755 --- a/src/main/php/text/json/patch/ArrayIndex.class.php +++ b/src/main/php/text/json/patch/ArrayIndex.class.php @@ -11,6 +11,8 @@ class ArrayIndex extends Address { */ public function __construct($pos, parent $parent) { $this->pos= $pos; + is_object($parent->reference) && $parent->reference= (array)$parent->reference; + if (is_array($parent->reference) && array_key_exists($this->pos, $parent->reference)) { parent::__construct($parent->reference[$this->pos], $parent); } else { diff --git a/src/test/php/text/json/patch/unittest/PointerTest.class.php b/src/test/php/text/json/patch/unittest/PointerTest.class.php index e3058a4..67dd019 100755 --- a/src/test/php/text/json/patch/unittest/PointerTest.class.php +++ b/src/test/php/text/json/patch/unittest/PointerTest.class.php @@ -234,6 +234,13 @@ public function dash_for_object() { Assert::equals(['key' => 'value', '-' => 'added'], $value); } + #[Test] + public function modify_array_index_of_object() { + $value= (object)['1' => 'test']; + (new Pointer('/1'))->resolve($value)->modify('changed'); + Assert::equals(['1' => 'changed'], $value); + } + #[Test] public function dash_for_empty_object() { $value= (object)[];