-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
216 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,115 +3,253 @@ PHP Fast JSON Patch | |
|
||
![Test](https://github.com/blancks/fast-jsonpatch-php/workflows/Test/badge.svg) | ||
![phpstan](https://github.com/blancks/fast-jsonpatch-php/workflows/phpstan/badge.svg) | ||
[![codecov](https://codecov.io/github/blancks/fast-jsonpatch-php/graph/badge.svg?token=3PUC5RAPPQ)](https://codecov.io/github/blancks/fast-jsonpatch-php) | ||
[![codecov](https://codecov.io/gh/blancks/fast-jsonpatch-php/branch/dev-v2/graph/badge.svg?token=3PUC5RAPPQ)](https://codecov.io/gh/blancks/fast-jsonpatch-php) | ||
[![maintainability](https://api.codeclimate.com/v1/badges/44af70d9b23b5f6c7760/maintainability)](https://codeclimate.com/github/blancks/fast-jsonpatch-php) | ||
[![PHP Version Require](https://poser.pugx.org/blancks/fast-jsonpatch-php/require/php)](https://packagist.org/packages/blancks/fast-jsonpatch-php) | ||
[![Latest Stable Version](https://poser.pugx.org/blancks/fast-jsonpatch-php/v)](https://packagist.org/packages/blancks/fast-jsonpatch-php) | ||
|
||
FastJsonPatch is designed to handle JSON Patch operations in accordance with the [RFC 6902](http://tools.ietf.org/html/rfc6902) specification. | ||
|
||
JSON Patch is a format for expressing a sequence of operations to be applied to a JSON document. This class provides methods to parse, validate, and apply these operations, allowing you to modify JSON objects or arrays programmatically. | ||
This documentation covers the `FastJsonPatch` PHP class, designed to apply a series of JSON Patch operations as specified in [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902). JSON Patch is a format for describing changes to a JSON document. | ||
|
||
## Installation via Composer | ||
|
||
``` bash | ||
composer require blancks/fast-jsonpatch-php | ||
``` | ||
|
||
## Benchmark Results | ||
|
||
The following table shows the average time each library took to apply a patch with 1000 operations to a target document as summary of the performance. | ||
The benchmark script and full data is available at [blancks/php-jsonpatch-benchmarks](https://github.com/blancks/php-jsonpatch-benchmarks). | ||
|
||
| Library | Microseconds | | ||
|-----------------------------|--------------| | ||
| blancks/fast-jsonpatch-php | 2903 | | ||
| mikemccabe/json-patch-php | 3355 | | ||
| swaggest/json-diff | 3638 | | ||
| gamringer/php-json-patch | 7276 | | ||
| xp-forge/json-patch | 8534 | | ||
| php-jsonpatch/php-jsonpatch | 10970 | | ||
| remorhaz/php-json-patch | 870711 | | ||
|
||
Performance comparison between releases is still available [here](https://docs.google.com/spreadsheets/d/1YHVZ38GHf0v9nJMCz5Sx_Z5nz8_VKiwnDpaGnnHtiXQ/edit?usp=sharing) | ||
|
||
## Key features | ||
|
||
1. **Apply JSON Patch Operations:** | ||
- The class can apply a series of JSON Patch operations to a target JSON document. | ||
- The operations are performed sequentially, modifying the document as specified in the patch. | ||
|
||
|
||
2. **Operation Types:** | ||
- **add**: Adds a value to a specific location in the JSON document. | ||
- **copy**: Copies a value from one location to another within the JSON document. | ||
- **move**: Moves a value from one location to another within the JSON document. | ||
- **remove**: Removes a value from a specific location in the JSON document. | ||
- **replace**: Replaces the value at a specific location with a new value. | ||
- **test**: Tests whether a specified value is present at a specific location in the JSON document. | ||
|
||
|
||
3. **Path Parsing:** | ||
- The class uses JSON Pointer ([RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901)) notation to identify locations within the JSON document. It correctly handles the path syntax, including edge cases such as escaping special characters. | ||
|
||
|
||
4. **Validation:** | ||
- The class ensures that the provided patch document conforms to the JSON Patch specification, validating the structure and types of operations before applying them. | ||
--- | ||
|
||
## Class Overview | ||
|
||
5. **Performance:** | ||
- The class is optimized for performance, time complexity is O(N*P) where N is the number of operations of the patch and where P is the nesting level of patch operations. | ||
- Best use case is for scenarios where JSON document can be fully loaded into memory, and you need fast patch processing like websockets server/client. | ||
The `FastJsonPatch` class provides a way to modify JSON documents using a structured patch object. The patch object contains an array of operations (`add`, `remove`, `replace`, `move`, `copy`, and `test`) that describe the changes to be made to the target JSON document. | ||
|
||
--- | ||
|
||
6. **Tests:** | ||
- Extensive unit testing ensures that everything is robust and works as intended. | ||
### Usage Example | ||
|
||
## Basic Usage | ||
|
||
``` php | ||
<?php require_once 'vendor/autoload.php'; | ||
Below is an example of how to use the `FastJsonPatch` class to apply a patch to a JSON document: | ||
|
||
```php | ||
use blancks\JsonPatch\FastJsonPatch; | ||
use blancks\JsonPatch\FastJsonPatch\exceptions\FastJsonPatchException; | ||
|
||
$json = '{"name": "John", "age": 30}'; | ||
$document = '{"foo":"bar","baz":["qux","quux"]}'; | ||
|
||
$patch = '[ | ||
{"op": "replace", "path": "/name", "value": "Jane"}, | ||
{"op": "add", "path": "/email", "value": "[email protected]"} | ||
{"op":"replace","path":"\/baz\/1","value":"boo"}, | ||
{"op":"add","path":"\/hello","value":{"world":"wide"}}, | ||
{"op":"remove","path":"\/foo"} | ||
]'; | ||
|
||
try { | ||
|
||
echo FastJsonPatch::apply($json, $patch); | ||
// Output: {"name": "Jane", "age": 30, "email": "[email protected]"} | ||
|
||
} catch(FastJsonPatchException $e) { | ||
$FastJsonPatch = FastJsonPatch::fromJson($document); | ||
$FastJsonPatch->apply($patch); | ||
|
||
// FastJsonPatchException comes with two additional methods to fetch context data: | ||
// $e->getContextPointer() may return the context JSON pointer for given error | ||
// $e->getContextDocument() may return the portion of the document relevant for the error | ||
|
||
} | ||
print_r($FastJsonPatch->getDocument()); | ||
``` | ||
|
||
## Methods Overview | ||
|
||
- `apply(string $json, string $patch): string` Applies the $patch operations to the provided $json document and returns the updated json document string. | ||
|
||
|
||
- `applyDecoded(string $json, string $patch): mixed` Same as **apply** but returns the decoded document instead of a json string | ||
**Expected Output:** | ||
|
||
```php | ||
[ | ||
"baz" => ["qux", "boo"], | ||
"hello" => ["world" => "wide"] | ||
] | ||
``` | ||
|
||
- `applyByReference(array|\stdClass &$document, array $patch): void` References your in-memory representation of the document and applies the patch in place. | ||
If the document is already json-decoded in your code you can just pass it to the class constructor instead: | ||
|
||
```php | ||
use blancks\JsonPatch\FastJsonPatch; | ||
|
||
- `parsePath(string $json, string $pointer): mixed` Returns the value located by the given $pointer from the $json string document | ||
$document = [ | ||
"foo" => "bar", | ||
"baz" => ["qux", "quux"] | ||
]; | ||
|
||
$patch = '[ | ||
{"op":"replace","path":"\/baz\/1","value":"boo"}, | ||
{"op":"add","path":"\/hello","value":{"world":"wide"}}, | ||
{"op":"remove","path":"\/foo"} | ||
]'; | ||
|
||
- `parsePathByReference(array|\stdClass &$document, string $pointer): mixed` Same as **parsePath** but finds the location from your in-memory document | ||
$FastJsonPatch = new FastJsonPatch($document); | ||
$FastJsonPatch->apply($patch); | ||
|
||
// $document is edited by reference | ||
print_r($document); | ||
``` | ||
|
||
- `validatePatch(string $patch): void` Checks if the provided $patch is structurally valid | ||
--- | ||
|
||
## Constructor | ||
|
||
### `__construct(mixed &$document, ?JsonHandlerInterface $JsonHandler = null)` | ||
|
||
- **Description**: Initializes a new instance of the `FastJsonPatch` class. | ||
- **Parameters**: | ||
- `mixed &$document`: The decoded JSON document. | ||
- `?JsonHandlerInterface $JsonHandler`: An instance of the JSON handler which will be responsible for encoding/decoding and CRUD operations.\ | ||
The default handler is the `BasicJsonHandler` class and decodes json objects as php \stdClass instances. This is the recommended way.\ | ||
If you cannot avoid working with associative arrays, you can pass a `ArrayJsonHandler` instance instead. | ||
- **Returns**: Instance of the `FastJsonPatch` class. | ||
|
||
--- | ||
|
||
## Public Methods | ||
|
||
### `static function fromJson(string $patch, ?JsonHandlerInterface $JsonHandler = null) : void` | ||
|
||
- **Description**: Returns a new instance of the `FastJsonPatch` class. | ||
- **Parameters**: | ||
- `string $document`: A json encoded document to which the patches will be applied | ||
- `?JsonHandlerInterface $JsonHandler`: An instance of the JSON handler which will be responsible for encoding/decoding and CRUD operations.\ | ||
The default handler is the `BasicJsonHandler` class and decodes json objects as php \stdClass instances. This is the recommended way.\ | ||
If you cannot avoid working with associative arrays, you can pass a `ArrayJsonHandler` instance instead. | ||
- **Example**: | ||
```php | ||
$FastJsonPatch = FastJsonPatch::fromJson('{"foo":"bar","baz":["qux","quux"]}'); | ||
``` | ||
|
||
--- | ||
|
||
### `function apply(string $patch) : void` | ||
|
||
- **Description**: Applies a series of patch operations to the specified JSON document. Ensures atomicity by applying all operations successfully or making no changes at all if any operation fails. | ||
- **Parameters**: | ||
- `string $patch`: A json-encoded array of patch operations. | ||
- **Exceptions**: | ||
- Throws `FastJsonPatchValidationException` if a patch operation is invalid or improperly formatted. | ||
- Throws `FastJsonPatchException` if any other error occurs while applying the patch | ||
- **Example**: | ||
```php | ||
$FastJsonPatch->apply($patch); | ||
``` | ||
|
||
--- | ||
|
||
### `function isValidPatch(string $patch): bool` | ||
|
||
- **Description**: Tells if the $patch passes the validation | ||
- **Parameters**: | ||
- `string $patch`: A json-encoded array of patch operations. | ||
- **Returns**: True is the patch is valid, false otherwise | ||
- **Example**: | ||
```php | ||
$patch = '[{"op":"add","path":"/foo"}]'; // invalid because there's no "value" key | ||
|
||
if ($FastJsonPatch->isValidPatch($patch)) { | ||
$FastJsonPatch->apply($patch); | ||
} else { | ||
echo "Invalid patch!"; | ||
} | ||
``` | ||
|
||
--- | ||
|
||
### `function read(string $path): mixed` | ||
|
||
- **Description**: Uses a JSON Pointer (RFC-6901) to fetch data from the referenced document | ||
- **Parameters**: | ||
- `string $patch`: A json pointer | ||
- **Returns**: The value located by the provided pointer | ||
- **Example**: | ||
```php | ||
$FastJsonPatch = FastJsonPatch::fromJson('{"foo":"bar","baz":["qux","quux"]}') | ||
echo $FastJsonPatch->read('/baz/1'); // "quux" | ||
``` | ||
|
||
--- | ||
|
||
### `function &getDocument(): mixed` | ||
|
||
- **Description**: Returns the document reference that the instance is holding | ||
- **Returns**: The referenced document | ||
- **Example**: | ||
```php | ||
$FastJsonPatch = FastJsonPatch::fromJson('["qux","quux"]') | ||
var_dump($FastJsonPatch->getDocument()); // array(2) {[0]=> string(3) "qux" [1]=> string(4) "quux"} | ||
``` | ||
|
||
--- | ||
|
||
### `function registerOperation(PatchOperationInterface $PatchOperation): void` | ||
|
||
- **Description**: Allows to register new patch operation handlers or to override existing ones. | ||
- **Parameters**: | ||
- `PatchOperationInterface $PatchOperation`: The handler class for handling the operation. | ||
- **Example**: | ||
```php | ||
$FastJsonPatch->registerOperation(new Add); | ||
``` | ||
|
||
--- | ||
|
||
## Supported Operations | ||
|
||
#### `add` | ||
|
||
- **Description**: Adds a value to the specified path in the document. Creates any necessary intermediate nodes. | ||
- **Parameters**: | ||
- `path`: JSON Pointer to the location where the value should be added. | ||
- `value`: The value to add. | ||
- **Example**: | ||
```json | ||
{"op":"add","path":"/new/key","value":"example"} | ||
``` | ||
|
||
#### `remove` | ||
|
||
- **Description**: Removes the value at the specified path. | ||
- **Parameters**: | ||
- `path`: JSON Pointer to the location of the value to remove. | ||
- **Example**: | ||
```php | ||
{"op":"remove","path":"/old/key"} | ||
``` | ||
|
||
#### `replace` | ||
|
||
- **Description**: Replaces the value at the specified path with a new value. | ||
- **Parameters**: | ||
- `path`: JSON Pointer to the location of the value to replace. | ||
- `value`: The new value. | ||
- **Example**: | ||
```json | ||
{"op":"replace","path":"/replace/key","value":"newValue"} | ||
``` | ||
|
||
#### `move` | ||
|
||
- **Description**: Moves the value from one path to another. | ||
- **Parameters**: | ||
- `from`: JSON Pointer to the source location of the value to move. | ||
- `path`: JSON Pointer to the destination location. | ||
- **Example**: | ||
```json | ||
{"op":"move","from":"/old/key","path":"/new/key"} | ||
``` | ||
|
||
#### `copy` | ||
|
||
- **Description**: Copies the value from one path to another. | ||
- **Parameters**: | ||
- `from`: JSON Pointer to the source location of the value to copy. | ||
- `path`: JSON Pointer to the destination location. | ||
- **Example**: | ||
```json | ||
{"op":"copy","from":"/source/key","path":"/target/key"} | ||
``` | ||
|
||
#### `test` | ||
|
||
- **Description**: Tests that the specified value matches the value at the path. | ||
- **Parameters**: | ||
- `path`: JSON Pointer to the location of the value to test. | ||
- `value`: The value to compare. | ||
- **Example**: | ||
```json | ||
{"op":"test","path":"/test/key","value":"expectedValue"} | ||
``` | ||
|
||
--- | ||
|
||
## Running tests | ||
|
||
|
@@ -123,4 +261,4 @@ Test cases comes from [json-patch/json-patch-tests](https://github.com/json-patc | |
|
||
## License | ||
|
||
This software is licensed under the [MIT License](LICENSE.md). | ||
This software is licensed under the [MIT License](LICENSE.md). |