Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/JsonPointer.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
}
} else { // null or array
$intKey = filter_var($key, FILTER_VALIDATE_INT);

// Avoid PathException with objects like {"+1": ...}
$hasPlus = $intKey !== false &&
is_string($key) &&
$key !== '' &&
$key[0] === '+';
if ($hasPlus) {
$intKey = false;
}
Comment on lines +124 to +131

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

The fix correctly prevents filter_var('+1', FILTER_VALIDATE_INT) from returning integer 1, which would otherwise cause a PathException when the target data is an object (or an associative array treated as object). However, the same problem exists for keys that start with '-' (e.g., -1), because filter_var('-1', FILTER_VALIDATE_INT) returns integer -1. This integer can also lead to incorrect path resolution when the data is an object (e.g., $ref[-1] fails on a stdClass and may behave wrongly on arrays with string keys). The PR’s test includes a -1 scenario but does not confirm that the fix works for it; without handling the '-' prefix, the test may fail or behave inconsistently in certain data contexts (e.g., when the actual data is an object or an array with string keys). The impact is moderate: users with JSON objects whose keys are negative integers will still experience the original bug. A more complete fix should also set $intKey = false for keys starting with '-' (or any sign prefix followed by digits).

Code Suggestion:

// Avoid PathException with objects like {"+1": ...} or {"-1": ...}
$hasSign = $intKey !== false &&
    is_string($key) &&
    $key !== '' &&
    ($key[0] === '+' || $key[0] === '-');
if ($hasSign) {
    $intKey = false;
}


if ($ref === null && (false === $intKey || $intKey !== 0)) {
$key = (string)$key;
if ($flags & self::RECURSIVE_KEY_CREATION) {
Expand Down
32 changes: 31 additions & 1 deletion tests/src/AssociativeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,34 @@ public function testDiffAssociative()
$patch->apply($original);
$this->assertEquals($newJson, $original);
}
}

/**
* Avoid PathException with objects like {"+1": ...}
*
* @throws \Swaggest\JsonDiff\Exception
*/
public function testSignedIntegerLikeKey()
{
$original = [
'+1' => [
['name' => 'first', 'keep' => 1],
['name' => 'second', 'keep' => 2],
],
'-1' => [
['name' => 'other', 'keep' => 3],
],
];

$patch = JsonPatch::import([
['op' => 'replace', 'path' => '/+1/1/name', 'value' => 'patched'],
['op' => 'replace', 'path' => '/-1/0/name', 'value' => 'negative'],
]);
$patch->setFlags(JsonPatch::TOLERATE_ASSOCIATIVE_ARRAYS);
$patch->apply($original);

$this->assertSame('patched', $original['+1'][1]['name']);
$this->assertSame('negative', $original['-1'][0]['name']);
$this->assertSame('first', $original['+1'][0]['name']);
$this->assertArrayNotHasKey(1, $original);
}
}