swaggest / json-diff

JSON diff/rearrange/patch/pointer library for PHP
MIT License
220 stars 30 forks source link
diff hacktoberfest json

JSON diff/rearrange/patch/pointer library for PHP

A PHP implementation for finding unordered diff between two JSON documents.

Build Status Scrutinizer Code Quality Code Climate Code Coverage time tracker

Purpose

Installation

Library

git clone https://github.com/swaggest/json-diff.git

Composer

Install PHP Composer

composer require swaggest/json-diff

Library usage

JsonDiff

Create JsonDiff object from two values (original and new).

$r = new JsonDiff(json_decode($originalJson), json_decode($newJson));

On construction JsonDiff will build rearranged value of new recursively keeping original keys order where possible. Keys that are missing in original will be appended to the end of rearranged value in same order they had in new value.

If two values are arrays of objects, JsonDiff will try to find a common unique field in those objects and use it as criteria for rearranging. You can enable this behaviour with JsonDiff::REARRANGE_ARRAYS option:

$r = new JsonDiff(
    json_decode($originalJson), 
    json_decode($newJson),
    JsonDiff::REARRANGE_ARRAYS
);

Available options:

Options can be combined, e.g. JsonDiff::REARRANGE_ARRAYS + JsonDiff::STOP_ON_DIFF.

getDiffCnt

Returns total number of differences

getPatch

Returns JsonPatch of difference

getMergePatch

Returns JSON Merge Patch value of difference

getRearranged

Returns new value, rearranged with original order.

getRemoved

Returns removals as partial value of original.

getRemovedPaths

Returns list of JSON paths that were removed from original.

getRemovedCnt

Returns number of removals.

getAdded

Returns additions as partial value of new.

getAddedPaths

Returns list of JSON paths that were added to new.

getAddedCnt

Returns number of additions.

getModifiedOriginal

Returns modifications as partial value of original.

getModifiedNew

Returns modifications as partial value of new.

getModifiedDiff

Returns list of ModifiedPathDiff containing paths with original and new values.

Not collected by default, requires JsonDiff::COLLECT_MODIFIED_DIFF option.

getModifiedPaths

Returns list of JSON paths that were modified from original to new.

getModifiedCnt

Returns number of modifications.

JsonPatch

import

Creates JsonPatch instance from JSON-decoded data.

export

Creates patch data from JsonPatch object.

op

Adds operation to JsonPatch.

apply

Applies patch to JSON-decoded data.

setFlags

Alters default behavior.

Available flags:

JsonPointer

escapeSegment

Escapes path segment.

splitPath

Creates array of unescaped segments from JSON Pointer string.

buildPath

Creates JSON Pointer string from array of unescaped segments.

add

Adds value to data at path specified by segments.

get

Gets value from data at path specified by segments.

getByPointer

Gets value from data at path specified JSON Pointer string.

remove

Removes value from data at path specified by segments.

JsonMergePatch

apply

Applies patch to JSON-decoded data.

JsonValueReplace

process

Recursively replaces all nodes equal to search value with replace value.

Example

$originalJson = <<<'JSON'
{
    "key1": [4, 1, 2, 3],
    "key2": 2,
    "key3": {
        "sub0": 0,
        "sub1": "a",
        "sub2": "b"
    },
    "key4": [
        {"a":1, "b":true, "subs": [{"s":1}, {"s":2}, {"s":3}]}, {"a":2, "b":false}, {"a":3}
    ]
}
JSON;

$newJson = <<<'JSON'
{
    "key5": "wat",
    "key1": [5, 1, 2, 3],
    "key4": [
        {"c":false, "a":2}, {"a":1, "b":true, "subs": [{"s":3, "add": true}, {"s":2}, {"s":1}]}, {"c":1, "a":3}
    ],
    "key3": {
        "sub3": 0,
        "sub2": false,
        "sub1": "c"
    }
}
JSON;

$patchJson = <<<'JSON'
[
    {"value":4,"op":"test","path":"/key1/0"},
    {"value":5,"op":"replace","path":"/key1/0"},

    {"op":"remove","path":"/key2"},

    {"op":"remove","path":"/key3/sub0"},

    {"value":"a","op":"test","path":"/key3/sub1"},
    {"value":"c","op":"replace","path":"/key3/sub1"},

    {"value":"b","op":"test","path":"/key3/sub2"},
    {"value":false,"op":"replace","path":"/key3/sub2"},

    {"value":0,"op":"add","path":"/key3/sub3"},

    {"value":true,"op":"add","path":"/key4/0/subs/2/add"},

    {"op":"remove","path":"/key4/1/b"},

    {"value":false,"op":"add","path":"/key4/1/c"},

    {"value":1,"op":"add","path":"/key4/2/c"},

    {"value":"wat","op":"add","path":"/key5"}
]
JSON;

$diff = new JsonDiff(json_decode($originalJson), json_decode($newJson), JsonDiff::REARRANGE_ARRAYS);
$this->assertEquals(json_decode($patchJson), $diff->getPatch()->jsonSerialize());

$original = json_decode($originalJson);
$patch = JsonPatch::import(json_decode($patchJson));
$patch->apply($original);
$this->assertEquals($diff->getRearranged(), $original);

PHP Classes as JSON objects

Due to magical methods and other restrictions PHP classes can not be reliably mapped to/from JSON objects. There is support for objects of PHP classes in JsonPointer with limitations:

Arrays Rearrangement

When JsonDiff::REARRANGE_ARRAYS option is enabled, array items are ordered to match the original array.

If arrays contain homogenous objects, and those objects have a common property with unique values, array is ordered to match placement of items with same value of such property in the original array.

Example: original

[{"name": "Alex", "height": 180},{"name": "Joe", "height": 179},{"name": "Jane", "height": 165}]

vs new

[{"name": "Joe", "height": 179},{"name": "Jane", "height": 168},{"name": "Alex", "height": 180}]

would produce a patch:

[{"value":165,"op":"test","path":"/2/height"},{"value":168,"op":"replace","path":"/2/height"}]

If qualifying indexing property is not found, rearrangement is done based on items equality.

Example: original

{"data": [{"A": 1, "C": [1, 2, 3]}, {"B": 2}]}

vs new

{"data": [{"B": 2}, {"A": 1, "C": [3, 2, 1]}]}

would produce no difference.

CLI tool

Moved to swaggest/json-cli