Skip to content

Commit 3825404

Browse files
authoredSep 20, 2018
Merge pull request #15 from jsonCarmock/add-merge-patch
Add JSON Merge Patch support #13
2 parents 32f3f48 + a911cf2 commit 3825404

14 files changed

+914
-113
lines changed
 

‎.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
/phpunit.xml export-ignore
1212
/changelog.md export-ignore
1313
/Makefile export-ignore
14+
/.gitlab-ci.yml

‎.gitlab-ci.yml

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
before_script:
2+
- apt-get update -yqq
3+
- apt-get install git unzip -yqq
4+
- curl https://composer.github.io/installer.sig | tr -d '\n' > installer.sig
5+
- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
6+
- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
7+
- php composer-setup.php
8+
- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
9+
- php composer.phar install --prefer-dist --no-ansi --no-interaction --no-progress
10+
11+
test:5.6:
12+
image: php:5.6
13+
script:
14+
- pecl install xdebug-2.5.5
15+
- docker-php-ext-enable xdebug
16+
- vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
17+
18+
test:7.0:
19+
image: php:7.0
20+
script:
21+
- pecl install xdebug
22+
- docker-php-ext-enable xdebug
23+
- vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
24+
25+
test:7.1:
26+
image: php:7.1
27+
script:
28+
- pecl install xdebug
29+
- docker-php-ext-enable xdebug
30+
- vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
31+
32+
test:7.2:
33+
image: php:7.2
34+
script:
35+
- pecl install xdebug
36+
- docker-php-ext-enable xdebug
37+
- vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
38+
- curl https://github.com/phpstan/phpstan/releases/download/0.9.2/phpstan.phar -sLo ./phpstan.phar
39+
- php phpstan.phar analyze -l 7 ./src

‎.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ cache:
2020
# execute any number of scripts before the test run, custom env's are available as variables
2121
before_script:
2222
- composer install --dev --no-interaction --prefer-dist
23-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/phpstan.phar || wget https://github.com/phpstan/phpstan/releases/download/0.9.1/phpstan.phar -O $HOME/.composer/cache/phpstan.phar; fi
23+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/phpstan.phar || wget https://github.com/phpstan/phpstan/releases/download/0.9.2/phpstan.phar -O $HOME/.composer/cache/phpstan.phar; fi
2424
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/ocular.phar || wget https://scrutinizer-ci.com/ocular.phar -O $HOME/.composer/cache/ocular.phar; fi
2525
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/cctr || wget https://codeclimate.com/downloads/test-reporter/test-reporter-0.1.4-linux-amd64 -O $HOME/.composer/cache/cctr && chmod +x $HOME/.composer/cache/cctr; fi
2626
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr before-build; fi

‎README.md

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A PHP implementation for finding unordered diff between two `JSON` documents.
1313
* To detect breaking changes by analyzing removals and changes from original `JSON`.
1414
* To keep original order of object sets (for example `swagger.json` [parameters](https://swagger.io/docs/specification/describing-parameters/) list).
1515
* To make and apply JSON Patches, specified in [RFC 6902](http://tools.ietf.org/html/rfc6902) from the IETF.
16+
* To make and apply JSON Merge Patches, specified in [RFC 7386](https://tools.ietf.org/html/rfc7386) from the IETF.
1617
* To retrieve and modify data by [JSON Pointer](http://tools.ietf.org/html/rfc6901).
1718
* To recursively replace by JSON value.
1819

@@ -55,11 +56,23 @@ $r = new JsonDiff(
5556
);
5657
```
5758

59+
Available options:
60+
* `REARRANGE_ARRAYS` is an option to enable arrays rearrangement to minimize the difference.
61+
* `STOP_ON_DIFF` is an option to improve performance by stopping comparison when a difference is found.
62+
* `JSON_URI_FRAGMENT_ID` is an option to use URI Fragment Identifier Representation (example: "#/c%25d"). If not set default JSON String Representation (example: "/c%d").
63+
* `SKIP_JSON_PATCH` is an option to improve performance by not building JsonPatch for this diff.
64+
* `SKIP_JSON_MERGE_PATCH` is an option to improve performance by not building JSON Merge Patch value for this diff.
65+
66+
Options can be combined, e.g. `JsonDiff::REARRANGE_ARRAYS + JsonDiff::STOP_ON_DIFF`.
67+
5868
On created object you have several handy methods.
5969

6070
#### `getPatch`
6171
Returns [`JsonPatch`](#jsonpatch) of difference
6272

73+
#### `getMergePatch`
74+
Returns [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) value of difference
75+
6376
#### `getRearranged`
6477
Returns new value, rearranged with original order.
6578

@@ -137,6 +150,11 @@ Gets value from data at path specified `JSON Pointer` string.
137150
#### `remove`
138151
Removes value from data at path specified by segments.
139152

153+
### `JsonMergePatch`
154+
155+
#### `apply`
156+
Applies patch to `JSON`-decoded data.
157+
140158
### `JsonValueReplace`
141159

142160
#### `process`

‎composer.json

+32-29
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
{
2-
"name": "swaggest/json-diff",
3-
"description": "JSON diff/rearrange/patch/pointer library for PHP",
4-
"type": "library",
5-
"license": "MIT",
6-
"authors": [
7-
{
8-
"name": "Viacheslav Poturaev",
9-
"email": "vearutop@gmail.com"
10-
}
11-
],
12-
"require-dev": {
13-
"phpunit/phpunit": "^4.8.23",
14-
"phpunit/php-code-coverage": "2.2.4",
15-
"codeclimate/php-test-reporter": "^0.4.0"
16-
},
17-
"autoload": {
18-
"psr-4": {
19-
"Swaggest\\JsonDiff\\": "src/"
20-
}
21-
},
22-
"autoload-dev": {
23-
"psr-4": {
24-
"Swaggest\\JsonDiff\\Tests\\": "tests/src"
25-
}
26-
},
27-
"config": {
28-
"platform": {
29-
"php": "5.4.45"
30-
}
2+
"name": "swaggest/json-diff",
3+
"description": "JSON diff/rearrange/patch/pointer library for PHP",
4+
"type": "library",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Viacheslav Poturaev",
9+
"email": "vearutop@gmail.com"
3110
}
11+
],
12+
"require": {
13+
"ext-json": "*"
14+
},
15+
"require-dev": {
16+
"phpunit/phpunit": "^4.8.23",
17+
"phpunit/php-code-coverage": "2.2.4",
18+
"codeclimate/php-test-reporter": "^0.4.0"
19+
},
20+
"autoload": {
21+
"psr-4": {
22+
"Swaggest\\JsonDiff\\": "src/"
23+
}
24+
},
25+
"autoload-dev": {
26+
"psr-4": {
27+
"Swaggest\\JsonDiff\\Tests\\": "tests/src"
28+
}
29+
},
30+
"config": {
31+
"platform": {
32+
"php": "5.4.45"
33+
}
34+
}
3235
}

‎composer.lock

+205-73
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/JsonDiff.php

+73-9
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,41 @@
99

1010
class JsonDiff
1111
{
12+
/**
13+
* REARRANGE_ARRAYS is an option to enable arrays rearrangement to minimize the difference.
14+
*/
1215
const REARRANGE_ARRAYS = 1;
16+
17+
/**
18+
* STOP_ON_DIFF is an option to improve performance by stopping comparison when a difference is found.
19+
*/
1320
const STOP_ON_DIFF = 2;
1421

1522
/**
16-
* Use URI Fragment Identifier Representation will be used (example: "#/c%25d").
23+
* JSON_URI_FRAGMENT_ID is an option to use URI Fragment Identifier Representation (example: "#/c%25d").
1724
* If not set default JSON String Representation (example: "/c%d").
1825
*/
1926
const JSON_URI_FRAGMENT_ID = 4;
2027

28+
/**
29+
* SKIP_JSON_PATCH is an option to improve performance by not building JsonPatch for this diff.
30+
*/
31+
const SKIP_JSON_PATCH = 8;
32+
33+
/**
34+
* SKIP_JSON_MERGE_PATCH is an option to improve performance by not building JSON Merge Patch value for this diff.
35+
*/
36+
const SKIP_JSON_MERGE_PATCH = 16;
37+
2138
private $options = 0;
2239
private $original;
2340
private $new;
2441

42+
/**
43+
* @var mixed Merge patch container
44+
*/
45+
private $merge;
46+
2547
private $added;
2648
private $addedCnt = 0;
2749
private $addedPaths = array();
@@ -44,15 +66,16 @@ class JsonDiff
4466
private $jsonPatch;
4567

4668
/**
47-
* Processor constructor.
4869
* @param mixed $original
4970
* @param mixed $new
5071
* @param int $options
5172
* @throws Exception
5273
*/
5374
public function __construct($original, $new, $options = 0)
5475
{
55-
$this->jsonPatch = new JsonPatch();
76+
if (!($options & self::SKIP_JSON_PATCH)) {
77+
$this->jsonPatch = new JsonPatch();
78+
}
5679

5780
$this->original = $original;
5881
$this->new = $new;
@@ -63,6 +86,9 @@ public function __construct($original, $new, $options = 0)
6386
}
6487

6588
$this->rearranged = $this->rearrange();
89+
if (($new !== null) && $this->merge === null) {
90+
$this->merge = new \stdClass();
91+
}
6692
}
6793

6894
/**
@@ -182,6 +208,15 @@ public function getPatch()
182208
return $this->jsonPatch;
183209
}
184210

211+
/**
212+
* Returns JSON Merge Patch value of difference
213+
*/
214+
public function getMergePatch()
215+
{
216+
return $this->merge;
217+
218+
}
219+
185220
/**
186221
* @return array|null|object|\stdClass
187222
* @throws Exception
@@ -199,6 +234,8 @@ private function rearrange()
199234
*/
200235
private function process($original, $new)
201236
{
237+
$merge = !($this->options & self::SKIP_JSON_MERGE_PATCH);
238+
202239
if (
203240
(!$original instanceof \stdClass && !is_array($original))
204241
|| (!$new instanceof \stdClass && !is_array($new))
@@ -210,12 +247,17 @@ private function process($original, $new)
210247
}
211248
$this->modifiedPaths [] = $this->path;
212249

213-
$this->jsonPatch->op(new Test($this->path, $original));
214-
$this->jsonPatch->op(new Replace($this->path, $new));
250+
if ($this->jsonPatch !== null) {
251+
$this->jsonPatch->op(new Test($this->path, $original));
252+
$this->jsonPatch->op(new Replace($this->path, $new));
253+
}
215254

216255
JsonPointer::add($this->modifiedOriginal, $this->pathItems, $original);
217256
JsonPointer::add($this->modifiedNew, $this->pathItems, $new);
218257

258+
if ($merge) {
259+
JsonPointer::add($this->merge, $this->pathItems, $new, JsonPointer::RECURSIVE_KEY_CREATION);
260+
}
219261
}
220262
return $new;
221263
}
@@ -234,6 +276,15 @@ private function process($original, $new)
234276
$isArray = is_array($original);
235277
$removedOffset = 0;
236278

279+
if ($merge && is_array($new) && !is_array($original)) {
280+
$merge = false;
281+
JsonPointer::add($this->merge, $this->pathItems, $new);
282+
} elseif ($merge && $new instanceof \stdClass && !$original instanceof \stdClass) {
283+
$merge = false;
284+
JsonPointer::add($this->merge, $this->pathItems, $new);
285+
}
286+
287+
$isUriFragment = (bool)($this->options & self::JSON_URI_FRAGMENT_ID);
237288
foreach ($originalKeys as $key => $originalValue) {
238289
if ($this->options & self::STOP_ON_DIFF) {
239290
if ($this->modifiedCnt || $this->addedCnt || $this->removedCnt) {
@@ -247,7 +298,7 @@ private function process($original, $new)
247298
if ($isArray && is_int($actualKey)) {
248299
$actualKey -= $removedOffset;
249300
}
250-
$this->path .= '/' . JsonPointer::escapeSegment($actualKey, $this->options & self::JSON_URI_FRAGMENT_ID);
301+
$this->path .= '/' . JsonPointer::escapeSegment((string)$actualKey, $isUriFragment);
251302
$this->pathItems[] = $actualKey;
252303

253304
if (array_key_exists($key, $newArray)) {
@@ -263,9 +314,15 @@ private function process($original, $new)
263314
$removedOffset++;
264315
}
265316

266-
$this->jsonPatch->op(new Remove($this->path));
317+
if ($this->jsonPatch !== null) {
318+
$this->jsonPatch->op(new Remove($this->path));
319+
}
267320

268321
JsonPointer::add($this->removed, $this->pathItems, $originalValue);
322+
if ($merge) {
323+
JsonPointer::add($this->merge, $this->pathItems, null);
324+
}
325+
269326
}
270327
$this->path = $path;
271328
$this->pathItems = $pathItems;
@@ -278,13 +335,19 @@ private function process($original, $new)
278335
return null;
279336
}
280337
$newOrdered[$key] = $value;
281-
$path = $this->path . '/' . JsonPointer::escapeSegment($key, $this->options & self::JSON_URI_FRAGMENT_ID);
338+
$path = $this->path . '/' . JsonPointer::escapeSegment($key, $isUriFragment);
282339
$pathItems = $this->pathItems;
283340
$pathItems[] = $key;
284341
JsonPointer::add($this->added, $pathItems, $value);
342+
if ($merge) {
343+
JsonPointer::add($this->merge, $pathItems, $value);
344+
}
345+
285346
$this->addedPaths [] = $path;
286347

287-
$this->jsonPatch->op(new Add($path, $value));
348+
if ($this->jsonPatch !== null) {
349+
$this->jsonPatch->op(new Add($path, $value));
350+
}
288351

289352
}
290353

@@ -302,6 +365,7 @@ private function rearrangeArray(array $original, array $new)
302365
$uniqueIdx = array();
303366

304367
// find unique key for all items
368+
/** @var mixed[string] $f */
305369
$f = get_object_vars($first);
306370
foreach ($f as $key => $value) {
307371
if (is_array($value) || $value instanceof \stdClass) {

‎src/JsonMergePatch.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Swaggest\JsonDiff;
4+
5+
6+
class JsonMergePatch
7+
{
8+
public static function apply(&$original, $patch)
9+
{
10+
if (null === $patch) {
11+
$original = null;
12+
} elseif (is_object($patch)) {
13+
foreach (get_object_vars($patch) as $key => $val) {
14+
if ($val === null) {
15+
unset($original->$key);
16+
} else {
17+
if (!is_object($original)) {
18+
$original = new \stdClass();
19+
}
20+
$branch = &$original->$key;
21+
if (null === $branch) {
22+
$branch = new \stdClass();
23+
}
24+
self::apply($branch, $val);
25+
}
26+
}
27+
} else {
28+
$original = $patch;
29+
}
30+
}
31+
}

‎src/JsonValueReplace.php

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public function __construct($search, $replace, $pathFilter = null)
3030
* Recursively replaces all nodes equal to `search` value with `replace` value.
3131
* @param mixed $data
3232
* @return mixed
33+
* @throws Exception
3334
*/
3435
public function process($data)
3536
{

‎tests/assets/merge-patch.json

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
[
2+
{
3+
"doc": {
4+
"a": "b"
5+
},
6+
"patch": {
7+
"a": "c"
8+
},
9+
"expected": {
10+
"a": "c"
11+
}
12+
},
13+
{
14+
"doc": {
15+
"a": "b"
16+
},
17+
"patch": {
18+
"b": "c"
19+
},
20+
"expected": {
21+
"a": "b",
22+
"b": "c"
23+
}
24+
},
25+
{
26+
"doc": {
27+
"a": "b"
28+
},
29+
"patch": {
30+
"a": null
31+
},
32+
"expected": {}
33+
},
34+
{
35+
"doc": {
36+
"a": "b",
37+
"b": "c"
38+
},
39+
"patch": {
40+
"a": null
41+
},
42+
"expected": {
43+
"b": "c"
44+
}
45+
},
46+
{
47+
"doc": {
48+
"a": [
49+
"b"
50+
]
51+
},
52+
"patch": {
53+
"a": "c"
54+
},
55+
"expected": {
56+
"a": "c"
57+
}
58+
},
59+
{
60+
"doc": {
61+
"a": "c"
62+
},
63+
"patch": {
64+
"a": [
65+
"b"
66+
]
67+
},
68+
"expected": {
69+
"a": [
70+
"b"
71+
]
72+
}
73+
},
74+
{
75+
"doc": {
76+
"a": {
77+
"b": "c"
78+
}
79+
},
80+
"patch": {
81+
"a": {
82+
"b": "d",
83+
"c": null
84+
}
85+
},
86+
"expected": {
87+
"a": {
88+
"b": "d"
89+
}
90+
}
91+
},
92+
{
93+
"doc": {
94+
"a": [
95+
{
96+
"b": "c"
97+
}
98+
]
99+
},
100+
"patch": {
101+
"a": [
102+
1
103+
]
104+
},
105+
"expected": {
106+
"a": [
107+
1
108+
]
109+
}
110+
},
111+
{
112+
"doc": [
113+
"a",
114+
"b"
115+
],
116+
"patch": [
117+
"c",
118+
"d"
119+
],
120+
"expected": [
121+
"c",
122+
"d"
123+
]
124+
},
125+
{
126+
"doc": {
127+
"a": "b"
128+
},
129+
"patch": [
130+
"c"
131+
],
132+
"expected": [
133+
"c"
134+
]
135+
},
136+
{
137+
"doc": {
138+
"a": "foo"
139+
},
140+
"patch": null,
141+
"expected": null
142+
},
143+
{
144+
"doc": {
145+
"a": "foo"
146+
},
147+
"patch": "bar",
148+
"expected": "bar"
149+
},
150+
{
151+
"doc": {
152+
"e": null
153+
},
154+
"patch": {
155+
"a": 1
156+
},
157+
"expected": {
158+
"e": null,
159+
"a": 1
160+
}
161+
},
162+
{
163+
"doc": [
164+
1,
165+
2
166+
],
167+
"patch": {
168+
"a": "b",
169+
"c": null
170+
},
171+
"expected": {
172+
"a": "b"
173+
}
174+
},
175+
{
176+
"doc": {},
177+
"patch": {
178+
"a": {
179+
"bb": {
180+
"ccc": null
181+
}
182+
}
183+
},
184+
"expected": {
185+
"a": {
186+
"bb": {}
187+
}
188+
}
189+
}
190+
]

‎tests/src/DiffTest.php

+25
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,29 @@ public function testStopOnDiff()
1818
$this->assertSame(1, $diff->getDiffCnt());
1919
}
2020

21+
/**
22+
* @throws \Swaggest\JsonDiff\Exception
23+
*/
24+
public function testSkipPatch()
25+
{
26+
$original = (object)(array("root" => (object)array("a" => 1, "b" => 2)));
27+
$new = (object)(array("root" => (object)array("b" => 3, "c" => 4)));
28+
29+
$diff = new JsonDiff($original, $new, JsonDiff::SKIP_JSON_PATCH);
30+
$this->assertSame(null, $diff->getPatch());
31+
$this->assertSame(3, $diff->getDiffCnt());
32+
$this->assertSame(1, $diff->getAddedCnt());
33+
$this->assertSame(1, $diff->getRemovedCnt());
34+
$this->assertSame(1, $diff->getModifiedCnt());
35+
36+
$diff = new JsonDiff($original, $new, JsonDiff::REARRANGE_ARRAYS);
37+
$this->assertEquals('[{"op":"remove","path":"/root/a"},{"value":2,"op":"test","path":"/root/b"},{"value":3,"op":"replace","path":"/root/b"},{"value":4,"op":"add","path":"/root/c"}]',
38+
json_encode($diff->getPatch(), JSON_UNESCAPED_SLASHES));
39+
$this->assertSame(3, $diff->getDiffCnt());
40+
$this->assertSame(1, $diff->getAddedCnt());
41+
$this->assertSame(1, $diff->getRemovedCnt());
42+
$this->assertSame(1, $diff->getModifiedCnt());
43+
44+
}
45+
2146
}

‎tests/src/MergePatchTest.php

+293
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
<?php
2+
3+
namespace Swaggest\JsonDiff\Tests;
4+
5+
6+
use Swaggest\JsonDiff\Exception;
7+
use Swaggest\JsonDiff\JsonDiff;
8+
use Swaggest\JsonDiff\JsonMergePatch;
9+
10+
class MergePatchTest extends \PHPUnit_Framework_TestCase
11+
{
12+
13+
public function testSimple()
14+
{
15+
$case = json_decode(<<<'JSON'
16+
{
17+
"doc": {},
18+
"patch": {
19+
"a": {
20+
"bb": {
21+
"ccc": null
22+
}
23+
}
24+
},
25+
"expected": {
26+
"a": {
27+
"bb": {}
28+
}
29+
}
30+
}
31+
JSON
32+
);
33+
$doc = $case->doc;
34+
$patch = $case->patch;
35+
$expected = $case->expected;
36+
37+
JsonMergePatch::apply($doc, $patch);
38+
39+
$this->assertEquals($expected, $doc);
40+
}
41+
42+
public function test13()
43+
{
44+
$case = json_decode(<<<'JSON'
45+
{
46+
"doc": [
47+
1,
48+
2
49+
],
50+
"patch": {
51+
"a": "b",
52+
"c": null
53+
},
54+
"expected": {
55+
"a": "b"
56+
}
57+
}
58+
JSON
59+
);
60+
$doc = $case->doc;
61+
$patch = $case->patch;
62+
$expected = $case->expected;
63+
64+
JsonMergePatch::apply($doc, $patch);
65+
66+
$this->assertEquals($expected, $doc);
67+
68+
}
69+
70+
public function testGetMergePatch()
71+
{
72+
$case = json_decode(<<<'JSON'
73+
{
74+
"doc": {
75+
"a": {
76+
"b": "c"
77+
}
78+
},
79+
"patch": {
80+
"a": {
81+
"b": "d",
82+
"c": null
83+
}
84+
},
85+
"expected": {
86+
"a": {
87+
"b": "d"
88+
}
89+
}
90+
}
91+
JSON
92+
);
93+
$doc = $case->doc;
94+
$expected = $case->expected;
95+
$diff = new JsonDiff($doc, $expected);
96+
$mergePatch = $diff->getMergePatch();
97+
JsonMergePatch::apply($doc, $mergePatch);
98+
99+
$this->assertEquals($expected, $doc, 'Apply created failed: ' . json_encode(
100+
[
101+
"test" => $case,
102+
"patch" => $mergePatch,
103+
"result" => $doc,
104+
], JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT));
105+
106+
}
107+
108+
109+
public function testGetMergePatchOfArray()
110+
{
111+
$case = json_decode(<<<'JSON'
112+
{
113+
"doc": {
114+
"a": "b"
115+
},
116+
"patch": [
117+
"c"
118+
],
119+
"expected": [
120+
"c"
121+
]
122+
}
123+
JSON
124+
);
125+
$doc = $case->doc;
126+
$expected = $case->expected;
127+
$diff = new JsonDiff($doc, $expected);
128+
$mergePatch = $diff->getMergePatch();
129+
JsonMergePatch::apply($doc, $mergePatch);
130+
131+
$this->assertEquals($expected, $doc, 'Apply created failed: ' . json_encode(
132+
[
133+
"test" => $case,
134+
"patch" => $mergePatch,
135+
"result" => $doc,
136+
], JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT));
137+
138+
}
139+
140+
141+
public function testSame()
142+
{
143+
$case = json_decode(<<<'JSON'
144+
{
145+
"doc": {
146+
"a": {
147+
"b": "d"
148+
}
149+
},
150+
"patch": {
151+
"a": {
152+
"b": "d"
153+
}
154+
},
155+
"expected": {
156+
"a": {
157+
"b": "d"
158+
}
159+
}
160+
}
161+
JSON
162+
);
163+
$doc = $case->doc;
164+
$expected = $case->expected;
165+
$diff = new JsonDiff($doc, $expected);
166+
//print_r($diff->getPatch());
167+
$mergePatch = $diff->getMergePatch();
168+
JsonMergePatch::apply($doc, $mergePatch);
169+
170+
$this->assertEquals($expected, $doc, 'Apply created failed: ' . json_encode(
171+
[
172+
"test" => $case,
173+
"patch" => $mergePatch,
174+
"result" => $doc,
175+
], JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT));
176+
177+
}
178+
179+
public function testArraySwap()
180+
{
181+
$case = json_decode(<<<'JSON'
182+
{
183+
"doc": [
184+
"a",
185+
"b"
186+
],
187+
"patch": [
188+
"c",
189+
"d"
190+
],
191+
"expected": [
192+
"c",
193+
"d"
194+
]
195+
}
196+
JSON
197+
);
198+
$doc = $case->doc;
199+
$expected = $case->expected;
200+
$diff = new JsonDiff($doc, $expected);
201+
//print_r($diff->getPatch());
202+
$mergePatch = $diff->getMergePatch();
203+
JsonMergePatch::apply($doc, $mergePatch);
204+
205+
$this->assertEquals($expected, $doc, 'Apply created failed: ' . json_encode(
206+
[
207+
"test" => $case,
208+
"patch" => $mergePatch,
209+
"result" => $doc,
210+
], JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT));
211+
212+
213+
}
214+
215+
216+
/**
217+
* @dataProvider specTestsProvider
218+
*/
219+
public function testSpec($case)
220+
{
221+
$this->doTest($case);
222+
}
223+
224+
public function specTestsProvider()
225+
{
226+
return $this->provider(__DIR__ . '/../assets/merge-patch.json');
227+
}
228+
229+
protected function provider($path)
230+
{
231+
$cases = json_decode(file_get_contents($path));
232+
233+
$testCases = array();
234+
foreach ($cases as $i => $case) {
235+
if (!isset($case->comment)) {
236+
$comment = 'unknown' . $i;
237+
} else {
238+
$comment = $case->comment;
239+
}
240+
241+
$testCases[$comment] = array(
242+
'case' => $case,
243+
);
244+
}
245+
return $testCases;
246+
}
247+
248+
protected function doTest($case)
249+
{
250+
$case = clone $case;
251+
252+
if (!is_object($case->doc)) {
253+
$doc = $case->doc;
254+
} else {
255+
$doc = clone $case->doc;
256+
}
257+
$patch = $case->patch;
258+
$expected = isset($case->expected) ? $case->expected : null;
259+
$jsonOptions = JSON_UNESCAPED_SLASHES + JSON_PRETTY_PRINT;
260+
261+
262+
JsonMergePatch::apply($doc, $patch);
263+
$this->assertEquals($expected, $doc, 'Apply failed: ' . json_encode(
264+
[
265+
"test" => $case,
266+
"result" => $doc,
267+
], $jsonOptions));
268+
269+
if (!is_object($case->doc)) {
270+
$doc = $case->doc;
271+
} else {
272+
$doc = clone $case->doc;
273+
}
274+
try {
275+
$diff = new JsonDiff($doc, $expected);
276+
$mergePatch = $diff->getMergePatch();
277+
} catch (Exception $exception) {
278+
$mergePatch = $exception->getMessage();
279+
}
280+
JsonMergePatch::apply($doc, $mergePatch);
281+
282+
$this->assertEquals($expected, $doc, 'Apply created failed: ' . json_encode(
283+
[
284+
"test" => $case,
285+
"patch" => $mergePatch,
286+
"result" => $doc,
287+
], $jsonOptions));
288+
289+
290+
}
291+
292+
293+
}

‎tests/src/RearrangeTest.php

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
class RearrangeTest extends \PHPUnit_Framework_TestCase
1010
{
11+
/**
12+
* @throws \Swaggest\JsonDiff\Exception
13+
*/
1114
public function testKeepOrder()
1215
{
1316
$originalJson = <<<'JSON'

‎tests/src/SpecTest.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ protected function provider($path)
5656
return $testCases;
5757
}
5858

59-
protected function doTest($case) {
59+
protected function doTest($case)
60+
{
6061
$case = clone $case;
6162

6263
if (isset($case->disabled) && $case->disabled) {

0 commit comments

Comments
 (0)
Please sign in to comment.