Skip to content

Commit cbe5f9a

Browse files
committed
Add JSON Patch processor
Signed-off-by: Francis Galiegue <[email protected]>
1 parent 2cce1a1 commit cbe5f9a

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@
115115
<artifactId>json-schema-avro</artifactId>
116116
<version>0.1.2</version>
117117
</dependency>
118+
<dependency>
119+
<groupId>com.github.fge</groupId>
120+
<artifactId>json-patch</artifactId>
121+
<version>1.0</version>
122+
</dependency>
118123
<dependency>
119124
<groupId>org.testng</groupId>
120125
<artifactId>testng</artifactId>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.github.fge.jsonpatch;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.github.fge.jsonschema.report.MessageProvider;
5+
import com.github.fge.jsonschema.report.ProcessingMessage;
6+
7+
public final class JsonPatchInput
8+
implements MessageProvider
9+
{
10+
private final JsonNode rawPatch;
11+
private final JsonNode node;
12+
13+
public JsonPatchInput(final JsonNode rawPatch, final JsonNode node)
14+
{
15+
this.rawPatch = rawPatch;
16+
this.node = node;
17+
}
18+
19+
public JsonNode getRawPatch()
20+
{
21+
return rawPatch;
22+
}
23+
24+
public JsonNode getNode()
25+
{
26+
return node;
27+
}
28+
29+
@Override
30+
public ProcessingMessage newMessage()
31+
{
32+
return new ProcessingMessage().put("patch", rawPatch).put("node", node);
33+
}
34+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.github.fge.jsonpatch;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.github.fge.jackson.JsonLoader;
5+
import com.github.fge.jsonschema.exceptions.ProcessingException;
6+
import com.github.fge.jsonschema.main.JsonSchema;
7+
import com.github.fge.jsonschema.main.JsonSchemaFactory;
8+
import com.github.fge.jsonschema.processing.Processor;
9+
import com.github.fge.jsonschema.report.ProcessingReport;
10+
import com.github.fge.jsonschema.util.ValueHolder;
11+
12+
import java.io.IOException;
13+
14+
public final class JsonPatchProcessor
15+
implements Processor<JsonPatchInput, ValueHolder<JsonNode>>
16+
{
17+
private static final JsonSchema SCHEMA;
18+
19+
static {
20+
final JsonSchemaFactory factory = JsonSchemaFactory.byDefault();
21+
try {
22+
final JsonNode node = JsonLoader.fromResource("/json-patch.json");
23+
SCHEMA = factory.getJsonSchema(node);
24+
} catch (ProcessingException e) {
25+
throw new ExceptionInInitializerError(e);
26+
} catch (IOException e) {
27+
throw new ExceptionInInitializerError(e);
28+
}
29+
}
30+
31+
@Override
32+
public ValueHolder<JsonNode> process(final ProcessingReport report,
33+
final JsonPatchInput input)
34+
throws ProcessingException
35+
{
36+
final JsonNode rawPatch = input.getRawPatch();
37+
report.mergeWith(SCHEMA.validate(rawPatch));
38+
if (!report.isSuccess())
39+
throw new ProcessingException(input.newMessage()
40+
.message("illegal JSON patch"));
41+
42+
try {
43+
final JsonPatch patch = JsonPatch.fromJson(rawPatch);
44+
final JsonNode ret = patch.apply(input.getNode());
45+
return ValueHolder.hold("result", ret);
46+
} catch (JsonPatchException e) {
47+
throw new ProcessingException(input.newMessage()
48+
.message("failed to apply patch"), e);
49+
} catch (IOException e) {
50+
throw new ProcessingException(input.newMessage()
51+
.message("patch considered invalid but schema validated it"),
52+
e);
53+
}
54+
}
55+
}

src/main/resources/json-patch.json

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
{
2+
"title": "JSON Patch",
3+
"description": "A JSON Schema describing a JSON Patch",
4+
"$schema": "http://json-schema.org/draft-04/schema#",
5+
"notes": [
6+
"Only required members are accounted for, other members are ignored"
7+
],
8+
"type": "array",
9+
"items": {
10+
"description": "one JSON Patch operation",
11+
"allOf": [
12+
{
13+
"description": "Members common to all operations",
14+
"type": "object",
15+
"required": [ "op", "path" ],
16+
"properties": {
17+
"path": { "$ref": "#/definitions/jsonPointer" }
18+
}
19+
},
20+
{ "$ref": "#/definitions/oneOperation" }
21+
]
22+
},
23+
"definitions": {
24+
"jsonPointer": {
25+
"type": "string",
26+
"pattern": "^(/[^/~]*(~[01][^/~]*)*)*$"
27+
},
28+
"add": {
29+
"description": "add operation. Value can be any JSON value.",
30+
"properties": { "op": { "enum": [ "add" ] } },
31+
"required": [ "value" ]
32+
},
33+
"remove": {
34+
"description": "remove operation. Only a path is specified.",
35+
"properties": { "op": { "enum": [ "remove" ] } }
36+
},
37+
"replace": {
38+
"description": "replace operation. Value can be any JSON value.",
39+
"properties": { "op": { "enum": [ "replace" ] } },
40+
"required": [ "value" ]
41+
},
42+
"move": {
43+
"description": "move operation. \"from\" is a JSON Pointer.",
44+
"properties": {
45+
"op": { "enum": [ "move" ] },
46+
"from": { "$ref": "#/definitions/jsonPointer" }
47+
},
48+
"required": [ "from" ]
49+
},
50+
"copy": {
51+
"description": "copy operation. \"from\" is a JSON Pointer.",
52+
"properties": {
53+
"op": { "enum": [ "copy" ] },
54+
"from": { "$ref": "#/definitions/jsonPointer" }
55+
},
56+
"required": [ "from" ]
57+
},
58+
"test": {
59+
"description": "test operation. Value can be any JSON value.",
60+
"properties": { "op": { "enum": [ "test" ] } },
61+
"required": [ "value" ]
62+
},
63+
"oneOperation": {
64+
"oneOf": [
65+
{ "$ref": "#/definitions/add" },
66+
{ "$ref": "#/definitions/remove" },
67+
{ "$ref": "#/definitions/replace" },
68+
{ "$ref": "#/definitions/move" },
69+
{ "$ref": "#/definitions/copy" },
70+
{ "$ref": "#/definitions/test" }
71+
]
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)