Skip to content

bumpcore/json-patch

Repository files navigation

BumpCore JSON Patch

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.

Table Of Contents

Version Table

BumpCore JSON Patch PHP
0.x ^8.3

Installation

Install the package with Composer:

composer require bumpcore/json-patch

Quick Start

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"]}

Choosing a Patch Format

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.

JSON Patch

JsonPatch implements RFC 6902.

Applying JSON Patch Documents

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.

Generating JSON Patch Documents

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"}]

Supported Operations

  • add
  • remove
  • replace
  • move
  • copy
  • test

The package exposes the RFC media type:

JsonPatch::MEDIA_TYPE; // application/json-patch+json

JSON Merge Patch

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:

  • Object members are added or replaced.
  • Object members set to null are removed.
  • Arrays are replaced as whole values.
  • Non-object patches replace the whole target document.

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

JSON Pointer

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.

JSON Values in PHP

The PHP-value APIs accept JSON-like PHP data:

  • PHP lists are treated as JSON arrays.
  • stdClass objects are treated as JSON objects.
  • Non-list PHP arrays are treated as JSON objects for ergonomic PHP usage.
  • Non-finite floats and non-JSON PHP values are rejected.

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"}}

Exceptions

All package-specific failures extend:

BumpCore\JsonPatch\Exception\JsonPatchException

More specific exceptions are available:

  • InvalidPatchException
  • JsonPointerException
  • TestFailedException

Patch 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

Testing

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.

Contribution

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

Changelog

See CHANGELOG.md for version history.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

RFC 6902 JSON Patch and RFC 7396 JSON Merge Patch implementation for PHP.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages