Skip to content

Commit

Permalink
Merge pull request #47 from gravity9-tech/feature/37_test_with_null_a…
Browse files Browse the repository at this point in the history
…gains_empty_path

Feature/37 Add element if field does not exist or it is equal to null
  • Loading branch information
piotr-bugara-gravity9 authored Jun 30, 2023
2 parents 41984a2 + 28417c2 commit 7c7f201
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 19 deletions.
124 changes: 124 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Its features are:
* {de,}serialization of JSON Patch and JSON Merge Patch instances with Jackson;
* full support for RFC 6902 operations, including `test`;
* JSON "diff" (RFC 6902 only) with operation factorization.
* support for `JsonPointer` and `JsonPath`

## Versions

Expand Down Expand Up @@ -266,6 +267,129 @@ final JsonNode patched = patch.apply(orig);
}
```
<br />

### Add if not exists
It's possible to add element to JsonNode if it does not exist using JsonPath expressions [see more examples of JsonPath](#jsonpath-examples)
* Add `color` field to `bicycle` object if it doesn't exist
`{ "op": "add", "path": "$.store.bicycle[?([email protected])].color", "value": "red" }`

Before:
```json
{
"store": {
"bicycle": {
"price": 19.95
}
}
}
```

After:
```json
{
"store": {
"bicycle": {
"price": 19.95,
"color": "red"
}
}
}
```
* Add value for `color` field to `bicycle` object if it is equal to `null`
`{ "op": "add", "path": "$.store.bicycle[?(@.color == null)].color", "value": "red" }`

Before:
```json
{
"store": {
"bicycle": {
"price": 19.95,
"color": null
}
}
}
```

After:
```json
{
"store": {
"bicycle": {
"price": 19.95,
"color": "red"
}
}
}
```

* Add field `pages` to `book` array if `book` does not contain this field, or it is equal to `null`
`{ "op": "add", "path": "$..book[?([email protected] || @.pages == null)].pages", "value": 250 }`

Before:
```json
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99,
"pages": null
},
{
"category": "fiction",
"author": "J.R.R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99,
"pages": 100
}
]
}
}
```

After:
```json
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95,
"pages": 250
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99,
"pages": 250
},
{
"category": "fiction",
"author": "J.R.R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99,
"pages": 100
}
]
}
}
```

### Remove operation

* Remove element with name `a`
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/gravity9/jsonpatch/CopyOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public CopyOperation(@JsonProperty("from") final String from, @JsonProperty("pat

@Override
public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
final String jsonPath = JsonPathParser.tmfStringToJsonPath(from);
final String jsonPath = JsonPathParser.parsePathToJsonPath(from);
final JsonNode dupData = JsonPath.parse(node.deepCopy()).read(jsonPath);
return new AddOperation(path, dupData).apply(node);
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/com/gravity9/jsonpatch/JsonPathParser.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.gravity9.jsonpatch;

public class JsonPathParser {
class JsonPathParser {

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

public static String tmfStringToJsonPath(String path) throws JsonPatchException {
/**
* Method parses JsonPointer or JsonPath path to JsonPath syntax
* @param path String containing JsonPath or JsonPointer expression
* @return String containing JsonPath expression
* @throws JsonPatchException throws when invalid JsonPointer expression provided
*/
static String parsePathToJsonPath(String path) throws JsonPatchException {
if (path.startsWith("$")) {
return path;
} else if (path.contains("?")) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/gravity9/jsonpatch/MoveOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
if (from.equals(path)) {
return node.deepCopy();
}
String jsonPath = JsonPathParser.tmfStringToJsonPath(from);
String jsonPath = JsonPathParser.parsePathToJsonPath(from);
final JsonNode movedNode = JsonPath.parse(node.deepCopy()).read(jsonPath, JsonNode.class);
final JsonPatchOperation remove = new RemoveOperation(from);
final JsonPatchOperation add = new AddOperation(path, movedNode);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/gravity9/jsonpatch/PathParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class PathParser {
* @throws JsonPatchException when invalid path provided
* */
public static PathDetails getParentPathAndNewNodeName(String path) throws JsonPatchException {
final String fullJsonPath = JsonPathParser.tmfStringToJsonPath(path);
final String fullJsonPath = JsonPathParser.parsePathToJsonPath(path);
final Path compiledPath = compilePath(fullJsonPath);
String[] splitJsonPath = splitJsonPath(compiledPath);

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/gravity9/jsonpatch/RemoveOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;

import java.io.IOException;

/**
Expand All @@ -51,23 +51,23 @@ public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
}

final DocumentContext nodeContext = JsonPath.parse(node.deepCopy());
final String jsonPath = JsonPathParser.tmfStringToJsonPath(path);
final String jsonPath = JsonPathParser.parsePathToJsonPath(path);
return nodeContext
.delete(jsonPath)
.read("$", JsonNode.class);
}

@Override
public void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException {
public void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
jgen.writeStartObject();
jgen.writeStringField("op", "remove");
jgen.writeStringField("path", path.toString());
jgen.writeStringField("path", path);
jgen.writeEndObject();
}

@Override
public void serializeWithType(final JsonGenerator jgen, final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException, JsonProcessingException {
throws IOException {
serialize(jgen, provider);
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/gravity9/jsonpatch/ReplaceOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public ReplaceOperation(@JsonProperty("path") final String path, @JsonProperty("

@Override
public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
final String jsonPath = JsonPathParser.tmfStringToJsonPath(path);
final String jsonPath = JsonPathParser.parsePathToJsonPath(path);
final DocumentContext nodeContext = JsonPath.parse(node.deepCopy());
final JsonNode replacement = value.deepCopy();
if (path.isEmpty()) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/gravity9/jsonpatch/TestOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public TestOperation(@JsonProperty("path") final String path, @JsonProperty("val

@Override
public JsonNode applyInternal(final JsonNode node) throws JsonPatchException {
final String jsonPath = JsonPathParser.tmfStringToJsonPath(path);
final String jsonPath = JsonPathParser.parsePathToJsonPath(path);
final JsonNode tested = JsonPath.parse(node.deepCopy()).read(jsonPath);
if (!EQUIVALENCE.equivalent(tested, value)) {
throw JsonPatchException.valueTestFailure(value, tested);
Expand Down
14 changes: 7 additions & 7 deletions src/test/java/com/gravity9/jsonpatch/JsonPathParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,51 +10,51 @@ public class JsonPathParserTest {
public void shouldConvertPointerToJsonPath() throws JsonPatchException {
String jsonPointerWithQuery = "/productPrice/prodPriceAlteration";
String expected = "$.productPrice.prodPriceAlteration";
String result = JsonPathParser.tmfStringToJsonPath(jsonPointerWithQuery);
String result = JsonPathParser.parsePathToJsonPath(jsonPointerWithQuery);
assertEquals(result, expected);
}

@Test
public void shouldConvertPointerWithArrayToJsonPath() throws JsonPatchException {
String jsonPointerWithQuery = "/productPrice/1/prodPriceAlteration";
String expected = "$.productPrice.[1].prodPriceAlteration";
String result = JsonPathParser.tmfStringToJsonPath(jsonPointerWithQuery);
String result = JsonPathParser.parsePathToJsonPath(jsonPointerWithQuery);
assertEquals(result, expected);
}

@Test
public void shouldConvertPointerWithArrayAtTheEndToJsonPath() throws JsonPatchException {
String jsonPointerWithQuery = "/productPrice/prodPriceAlteration/1";
String expected = "$.productPrice.prodPriceAlteration.[1]";
String result = JsonPathParser.tmfStringToJsonPath(jsonPointerWithQuery);
String result = JsonPathParser.parsePathToJsonPath(jsonPointerWithQuery);
assertEquals(result, expected);
}

@Test
public void shouldConvertArrayPathToJsonPath() throws JsonPatchException {
String jsonPointer = "/2/1/-";
String expected = "$.[2].[1].-";
String result = JsonPathParser.tmfStringToJsonPath(jsonPointer);
String result = JsonPathParser.parsePathToJsonPath(jsonPointer);
assertEquals(result, expected);
}

@Test
public void shouldLeaveJsonPathStatementsUntouched() throws JsonPatchException {
String filterQuery = "$.arrayPath[?(@.innerArray[?(@.nestedVal=='as')] empty false)].innerArray[?(@.nestedVal=='df')].name";
String expected = "$.arrayPath[?(@.innerArray[?(@.nestedVal=='as')] empty false)].innerArray[?(@.nestedVal=='df')].name";
String result = JsonPathParser.tmfStringToJsonPath(filterQuery);
String result = JsonPathParser.parsePathToJsonPath(filterQuery);
assertEquals(result, expected);
}

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

@Test(expectedExceptions = JsonPatchException.class)
public void shouldThrowExceptionWhenQuestionMarkInJsonPointerPath() throws JsonPatchException {
String filterQuery = "/characteristic/0/age?";
JsonPathParser.tmfStringToJsonPath(filterQuery);
JsonPathParser.parsePathToJsonPath(filterQuery);
}
}
Loading

0 comments on commit 7c7f201

Please sign in to comment.