Skip to content

Commit 7c7f201

Browse files
Merge pull request #47 from gravity9-tech/feature/37_test_with_null_agains_empty_path
Feature/37 Add element if field does not exist or it is equal to null
2 parents 41984a2 + 28417c2 commit 7c7f201

File tree

11 files changed

+438
-19
lines changed

11 files changed

+438
-19
lines changed

README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Its features are:
2929
* {de,}serialization of JSON Patch and JSON Merge Patch instances with Jackson;
3030
* full support for RFC 6902 operations, including `test`;
3131
* JSON "diff" (RFC 6902 only) with operation factorization.
32+
* support for `JsonPointer` and `JsonPath`
3233

3334
## Versions
3435

@@ -266,6 +267,129 @@ final JsonNode patched = patch.apply(orig);
266267
}
267268
```
268269
<br />
270+
271+
### Add if not exists
272+
It's possible to add element to JsonNode if it does not exist using JsonPath expressions [see more examples of JsonPath](#jsonpath-examples)
273+
* Add `color` field to `bicycle` object if it doesn't exist
274+
`{ "op": "add", "path": "$.store.bicycle[?(!@.color)].color", "value": "red" }`
275+
276+
Before:
277+
```json
278+
{
279+
"store": {
280+
"bicycle": {
281+
"price": 19.95
282+
}
283+
}
284+
}
285+
```
286+
287+
After:
288+
```json
289+
{
290+
"store": {
291+
"bicycle": {
292+
"price": 19.95,
293+
"color": "red"
294+
}
295+
}
296+
}
297+
```
298+
* Add value for `color` field to `bicycle` object if it is equal to `null`
299+
`{ "op": "add", "path": "$.store.bicycle[?(@.color == null)].color", "value": "red" }`
300+
301+
Before:
302+
```json
303+
{
304+
"store": {
305+
"bicycle": {
306+
"price": 19.95,
307+
"color": null
308+
}
309+
}
310+
}
311+
```
312+
313+
After:
314+
```json
315+
{
316+
"store": {
317+
"bicycle": {
318+
"price": 19.95,
319+
"color": "red"
320+
}
321+
}
322+
}
323+
```
324+
325+
* Add field `pages` to `book` array if `book` does not contain this field, or it is equal to `null`
326+
`{ "op": "add", "path": "$..book[?(!@.pages || @.pages == null)].pages", "value": 250 }`
327+
328+
Before:
329+
```json
330+
{
331+
"store": {
332+
"book": [
333+
{
334+
"category": "reference",
335+
"author": "Nigel Rees",
336+
"title": "Sayings of the Century",
337+
"price": 8.95
338+
},
339+
{
340+
"category": "fiction",
341+
"author": "Herman Melville",
342+
"title": "Moby Dick",
343+
"isbn": "0-553-21311-3",
344+
"price": 8.99,
345+
"pages": null
346+
},
347+
{
348+
"category": "fiction",
349+
"author": "J.R.R. Tolkien",
350+
"title": "The Lord of the Rings",
351+
"isbn": "0-395-19395-8",
352+
"price": 22.99,
353+
"pages": 100
354+
}
355+
]
356+
}
357+
}
358+
```
359+
360+
After:
361+
```json
362+
{
363+
"store": {
364+
"book": [
365+
{
366+
"category": "reference",
367+
"author": "Nigel Rees",
368+
"title": "Sayings of the Century",
369+
"price": 8.95,
370+
"pages": 250
371+
},
372+
{
373+
"category": "fiction",
374+
"author": "Herman Melville",
375+
"title": "Moby Dick",
376+
"isbn": "0-553-21311-3",
377+
"price": 8.99,
378+
"pages": 250
379+
},
380+
{
381+
"category": "fiction",
382+
"author": "J.R.R. Tolkien",
383+
"title": "The Lord of the Rings",
384+
"isbn": "0-395-19395-8",
385+
"price": 22.99,
386+
"pages": 100
387+
}
388+
]
389+
}
390+
}
391+
```
392+
269393
### Remove operation
270394

271395
* Remove element with name `a`

src/main/java/com/gravity9/jsonpatch/CopyOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public CopyOperation(@JsonProperty("from") final String from, @JsonProperty("pat
4949

5050
@Override
5151
public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
52-
final String jsonPath = JsonPathParser.tmfStringToJsonPath(from);
52+
final String jsonPath = JsonPathParser.parsePathToJsonPath(from);
5353
final JsonNode dupData = JsonPath.parse(node.deepCopy()).read(jsonPath);
5454
return new AddOperation(path, dupData).apply(node);
5555
}

src/main/java/com/gravity9/jsonpatch/JsonPathParser.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.gravity9.jsonpatch;
22

3-
public class JsonPathParser {
3+
class JsonPathParser {
44

55
private static final String ARRAY_ELEMENT_REGEX = "(?<=\\.)(\\d+)";
66

7-
public static String tmfStringToJsonPath(String path) throws JsonPatchException {
7+
/**
8+
* Method parses JsonPointer or JsonPath path to JsonPath syntax
9+
* @param path String containing JsonPath or JsonPointer expression
10+
* @return String containing JsonPath expression
11+
* @throws JsonPatchException throws when invalid JsonPointer expression provided
12+
*/
13+
static String parsePathToJsonPath(String path) throws JsonPatchException {
814
if (path.startsWith("$")) {
915
return path;
1016
} else if (path.contains("?")) {

src/main/java/com/gravity9/jsonpatch/MoveOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
7373
if (from.equals(path)) {
7474
return node.deepCopy();
7575
}
76-
String jsonPath = JsonPathParser.tmfStringToJsonPath(from);
76+
String jsonPath = JsonPathParser.parsePathToJsonPath(from);
7777
final JsonNode movedNode = JsonPath.parse(node.deepCopy()).read(jsonPath, JsonNode.class);
7878
final JsonPatchOperation remove = new RemoveOperation(from);
7979
final JsonPatchOperation add = new AddOperation(path, movedNode);

src/main/java/com/gravity9/jsonpatch/PathParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class PathParser {
1919
* @throws JsonPatchException when invalid path provided
2020
* */
2121
public static PathDetails getParentPathAndNewNodeName(String path) throws JsonPatchException {
22-
final String fullJsonPath = JsonPathParser.tmfStringToJsonPath(path);
22+
final String fullJsonPath = JsonPathParser.parsePathToJsonPath(path);
2323
final Path compiledPath = compilePath(fullJsonPath);
2424
String[] splitJsonPath = splitJsonPath(compiledPath);
2525

src/main/java/com/gravity9/jsonpatch/RemoveOperation.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
import com.fasterxml.jackson.annotation.JsonCreator;
2323
import com.fasterxml.jackson.annotation.JsonProperty;
2424
import com.fasterxml.jackson.core.JsonGenerator;
25-
import com.fasterxml.jackson.core.JsonProcessingException;
2625
import com.fasterxml.jackson.databind.JsonNode;
2726
import com.fasterxml.jackson.databind.SerializerProvider;
2827
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
2928
import com.fasterxml.jackson.databind.node.MissingNode;
3029
import com.jayway.jsonpath.DocumentContext;
3130
import com.jayway.jsonpath.JsonPath;
31+
3232
import java.io.IOException;
3333

3434
/**
@@ -51,23 +51,23 @@ public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
5151
}
5252

5353
final DocumentContext nodeContext = JsonPath.parse(node.deepCopy());
54-
final String jsonPath = JsonPathParser.tmfStringToJsonPath(path);
54+
final String jsonPath = JsonPathParser.parsePathToJsonPath(path);
5555
return nodeContext
5656
.delete(jsonPath)
5757
.read("$", JsonNode.class);
5858
}
5959

6060
@Override
61-
public void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException {
61+
public void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
6262
jgen.writeStartObject();
6363
jgen.writeStringField("op", "remove");
64-
jgen.writeStringField("path", path.toString());
64+
jgen.writeStringField("path", path);
6565
jgen.writeEndObject();
6666
}
6767

6868
@Override
6969
public void serializeWithType(final JsonGenerator jgen, final SerializerProvider provider, final TypeSerializer typeSer)
70-
throws IOException, JsonProcessingException {
70+
throws IOException {
7171
serialize(jgen, provider);
7272
}
7373

src/main/java/com/gravity9/jsonpatch/ReplaceOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public ReplaceOperation(@JsonProperty("path") final String path, @JsonProperty("
4343

4444
@Override
4545
public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
46-
final String jsonPath = JsonPathParser.tmfStringToJsonPath(path);
46+
final String jsonPath = JsonPathParser.parsePathToJsonPath(path);
4747
final DocumentContext nodeContext = JsonPath.parse(node.deepCopy());
4848
final JsonNode replacement = value.deepCopy();
4949
if (path.isEmpty()) {

src/main/java/com/gravity9/jsonpatch/TestOperation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public TestOperation(@JsonProperty("path") final String path, @JsonProperty("val
4949

5050
@Override
5151
public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
52-
final String jsonPath = JsonPathParser.tmfStringToJsonPath(path);
52+
final String jsonPath = JsonPathParser.parsePathToJsonPath(path);
5353
final JsonNode tested = JsonPath.parse(node.deepCopy()).read(jsonPath);
5454
if (!EQUIVALENCE.equivalent(tested, value)) {
5555
throw JsonPatchException.valueTestFailure(value, tested);

src/test/java/com/gravity9/jsonpatch/JsonPathParserTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,51 +10,51 @@ public class JsonPathParserTest {
1010
public void shouldConvertPointerToJsonPath() throws JsonPatchException {
1111
String jsonPointerWithQuery = "/productPrice/prodPriceAlteration";
1212
String expected = "$.productPrice.prodPriceAlteration";
13-
String result = JsonPathParser.tmfStringToJsonPath(jsonPointerWithQuery);
13+
String result = JsonPathParser.parsePathToJsonPath(jsonPointerWithQuery);
1414
assertEquals(result, expected);
1515
}
1616

1717
@Test
1818
public void shouldConvertPointerWithArrayToJsonPath() throws JsonPatchException {
1919
String jsonPointerWithQuery = "/productPrice/1/prodPriceAlteration";
2020
String expected = "$.productPrice.[1].prodPriceAlteration";
21-
String result = JsonPathParser.tmfStringToJsonPath(jsonPointerWithQuery);
21+
String result = JsonPathParser.parsePathToJsonPath(jsonPointerWithQuery);
2222
assertEquals(result, expected);
2323
}
2424

2525
@Test
2626
public void shouldConvertPointerWithArrayAtTheEndToJsonPath() throws JsonPatchException {
2727
String jsonPointerWithQuery = "/productPrice/prodPriceAlteration/1";
2828
String expected = "$.productPrice.prodPriceAlteration.[1]";
29-
String result = JsonPathParser.tmfStringToJsonPath(jsonPointerWithQuery);
29+
String result = JsonPathParser.parsePathToJsonPath(jsonPointerWithQuery);
3030
assertEquals(result, expected);
3131
}
3232

3333
@Test
3434
public void shouldConvertArrayPathToJsonPath() throws JsonPatchException {
3535
String jsonPointer = "/2/1/-";
3636
String expected = "$.[2].[1].-";
37-
String result = JsonPathParser.tmfStringToJsonPath(jsonPointer);
37+
String result = JsonPathParser.parsePathToJsonPath(jsonPointer);
3838
assertEquals(result, expected);
3939
}
4040

4141
@Test
4242
public void shouldLeaveJsonPathStatementsUntouched() throws JsonPatchException {
4343
String filterQuery = "$.arrayPath[?(@.innerArray[?(@.nestedVal=='as')] empty false)].innerArray[?(@.nestedVal=='df')].name";
4444
String expected = "$.arrayPath[?(@.innerArray[?(@.nestedVal=='as')] empty false)].innerArray[?(@.nestedVal=='df')].name";
45-
String result = JsonPathParser.tmfStringToJsonPath(filterQuery);
45+
String result = JsonPathParser.parsePathToJsonPath(filterQuery);
4646
assertEquals(result, expected);
4747
}
4848

4949
@Test(expectedExceptions = JsonPatchException.class, expectedExceptionsMessageRegExp = "Invalid path, `//` is not allowed in JsonPointer expressions.")
5050
public void shouldThrowExceptionWhenDoubleSlashesInJsonPointerPath() throws JsonPatchException {
5151
String filterQuery = "/characteristic/0//age";
52-
JsonPathParser.tmfStringToJsonPath(filterQuery);
52+
JsonPathParser.parsePathToJsonPath(filterQuery);
5353
}
5454

5555
@Test(expectedExceptions = JsonPatchException.class)
5656
public void shouldThrowExceptionWhenQuestionMarkInJsonPointerPath() throws JsonPatchException {
5757
String filterQuery = "/characteristic/0/age?";
58-
JsonPathParser.tmfStringToJsonPath(filterQuery);
58+
JsonPathParser.parsePathToJsonPath(filterQuery);
5959
}
6060
}

0 commit comments

Comments
 (0)