BumpCore JSON Patch is a dependency-free PHP package for working with JSON change documents. It supports RFC 6902 JSON Patch, RFC 7396 JSON Merge Patch, and RFC 6901 JSON Pointer.
Use it when you need to apply HTTP PATCH payloads, generate deterministic patch documents, address values inside JSON-like PHP data, or expose precise patch errors to API clients.
| BumpCore JSON Patch | PHP |
|---|---|
| 0.x | ^8.3 |
Install the package with Composer:
composer require bumpcore/json-patch
use BumpCore\JsonPatch\JsonPatch;
$document = ['foo' => ['bar', 'baz']];
$result = JsonPatch::apply($document, [
['op' => 'add', 'path' => '/foo/1', 'value' => 'qux'],
]);
// ['foo' => ['bar', 'qux', 'baz']]
For JSON string input and output:
use BumpCore\JsonPatch\JsonPatch;
$json = JsonPatch::applyJson(
'{"foo":["bar","baz"]}',
'[{"op":"add","path":"/foo/1","value":"qux"}]',
);
// {"foo":["bar","qux","baz"]}
This package supports two patch formats because they solve different problems.
Use JSON Patch when you need explicit operations:
[
{"op": "replace", "path": "/title", "value": "Hello!"},
{"op": "remove", "path": "/author/familyName"}
]
Use JSON Merge Patch when the patch should look like the document shape:
{
"title": "Hello!",
"author": {
"familyName": null
}
}
JSON Patch can update individual array elements. JSON Merge Patch replaces
arrays as whole values and uses null object members as removals.
JsonPatch implements RFC 6902.
use BumpCore\JsonPatch\JsonPatch;
$document = [
'title' => 'Goodbye!',
'tags' => ['example', 'sample'],
];
$patched = JsonPatch::apply($document, [
['op' => 'replace', 'path' => '/title', 'value' => 'Hello!'],
['op' => 'remove', 'path' => '/tags/1'],
['op' => 'add', 'path' => '/published', 'value' => true],
]);
// [
// 'title' => 'Hello!',
// 'tags' => ['example'],
// 'published' => true,
// ]
Patch application does not mutate the input document. It stops at the first failing operation, as RFC 6902 requires.
use BumpCore\JsonPatch\JsonPatch;
$source = ['name' => 'old', 'tags' => ['stable']];
$target = ['name' => 'new', 'tags' => ['stable', 'fast']];
$patch = JsonPatch::diff($source, $target);
// [
// ['op' => 'replace', 'path' => '/name', 'value' => 'new'],
// ['op' => 'add', 'path' => '/tags/-', 'value' => 'fast'],
// ]
JsonPatch::diff() generates readable add, remove, and replace
operations. It intentionally does not infer move or copy operations because
those require heuristic choices and can make generated patches harder to review.
For JSON text input and output:
use BumpCore\JsonPatch\JsonPatch;
$patchJson = JsonPatch::diffJson(
'{"name":"old"}',
'{"name":"new","enabled":true}',
);
// [{"op":"add","path":"/enabled","value":true},{"op":"replace","path":"/name","value":"new"}]
addremovereplacemovecopytestThe package exposes the RFC media type:
JsonPatch::MEDIA_TYPE; // application/json-patch+json
JsonMergePatch implements RFC 7396.
use BumpCore\JsonPatch\JsonMergePatch;
$document = [
'title' => 'Goodbye!',
'author' => [
'givenName' => 'John',
'familyName' => 'Doe',
],
'tags' => ['example', 'sample'],
];
$patched = JsonMergePatch::apply($document, [
'title' => 'Hello!',
'author' => [
'familyName' => null,
],
'tags' => ['example'],
]);
// [
// 'title' => 'Hello!',
// 'author' => ['givenName' => 'John'],
// 'tags' => ['example'],
// ]
Merge Patch rules are intentionally simple:
null are removed.For JSON text input and output:
use BumpCore\JsonPatch\JsonMergePatch;
$json = JsonMergePatch::applyJson(
'{"a":"b","c":{"d":"e","f":"g"}}',
'{"a":"z","c":{"f":null}}',
);
// {"a":"z","c":{"d":"e"}}
The package exposes the RFC media type:
JsonMergePatch::MEDIA_TYPE; // application/merge-patch+json
JsonPointer implements RFC 6901 pointer parsing and escaping.
use BumpCore\JsonPatch\JsonPointer;
$pointer = JsonPointer::fromString('/foo/~01');
$pointer->tokens(); // ['foo', '~1']
JsonPointer::escapeToken('a/b~c'); // a~1b~0c
JsonPointer::unescapeToken('a~1b~0c'); // a/b~c
Pointer helpers can also read and return modified copies of JSON-like values:
use BumpCore\JsonPatch\JsonPointer;
$document = ['items' => [['name' => 'Old']]];
JsonPointer::get($document, '/items/0/name'); // Old
JsonPointer::has($document, '/items/0/name'); // true
$updated = JsonPointer::set($document, '/items/0/name', 'New');
$removed = JsonPointer::remove($updated, '/items/0');
The helper methods do not mutate the original document.
The PHP-value APIs accept JSON-like PHP data:
stdClass objects are treated as JSON objects.When exact JSON shape matters, especially empty objects or numeric object member names, use the JSON string helpers:
use BumpCore\JsonPatch\JsonPatch;
$json = JsonPatch::applyJson(
'{"child":{}}',
'[{"op":"add","path":"/child/0","value":"kept as an object member"}]',
);
// {"child":{"0":"kept as an object member"}}
All package-specific failures extend:
BumpCore\JsonPatch\Exception\JsonPatchException
More specific exceptions are available:
InvalidPatchExceptionJsonPointerExceptionTestFailedExceptionPatch operation failures expose context when it is available:
$exception->operationIndex(); // 0
$exception->operation(); // remove
$exception->path(); // /items/0
$exception->from(); // /source for move/copy failures
Install development dependencies:
composer install
Run the test suite:
composer test
Run static analysis:
composer analyse
Check code style:
composer cs:check
Run the 100% coverage gate:
composer test:coverage
The test suite includes the active upstream json-patch/json-patch-tests
fixtures. Coverage requires PCOV, Xdebug, or phpdbg.
Contributions are welcome. If you find a bug or have a suggestion for improvement, please open an issue or create a pull request.
Please include tests for behavioral changes and run the quality checks before submitting a pull request:
composer cs:check
composer analyse
composer test
See CHANGELOG.md for version history.
The MIT License (MIT). Please see License File for more information.