Skip to content

Commit 2c00678

Browse files
cursoragentsimbo1905
andcommitted
Issue #130 Implement json-transforms parse/run core and JsonPath match locations
Co-authored-by: simbo1905 <simbo1905@60hertz.com>
1 parent aa63252 commit 2c00678

File tree

10 files changed

+1365
-0
lines changed

10 files changed

+1365
-0
lines changed

json-java21-jsonpath/src/main/java/json/java21/jsonpath/JsonPath.java

Lines changed: 309 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package json.java21.jsonpath;
2+
3+
import java.util.Objects;
4+
5+
/// A single step in a JsonPath match location.
6+
///
7+
/// Locations are expressed relative to the JSON value passed to `JsonPath.queryMatches(...)`.
8+
public sealed interface JsonPathLocationStep permits JsonPathLocationStep.Property, JsonPathLocationStep.Index {
9+
10+
/// Object member step (by property name).
11+
record Property(String name) implements JsonPathLocationStep {
12+
public Property {
13+
Objects.requireNonNull(name, "name must not be null");
14+
}
15+
}
16+
17+
/// Array element step (by index).
18+
record Index(int index) implements JsonPathLocationStep {}
19+
}
20+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package json.java21.jsonpath;
2+
3+
import jdk.sandbox.java.util.json.JsonValue;
4+
5+
import java.util.List;
6+
import java.util.Objects;
7+
8+
/// A single JsonPath match: the matched value plus its location.
9+
///
10+
/// The `location` is a list of steps from the queried root value to the match.
11+
/// The empty list indicates the root value itself.
12+
public record JsonPathMatch(List<JsonPathLocationStep> location, JsonValue value) {
13+
public JsonPathMatch {
14+
Objects.requireNonNull(location, "location must not be null");
15+
Objects.requireNonNull(value, "value must not be null");
16+
location = List.copyOf(location);
17+
}
18+
}
19+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package json.java21.transforms;
2+
3+
import jdk.sandbox.java.util.json.JsonObject;
4+
import jdk.sandbox.java.util.json.JsonValue;
5+
6+
import java.util.Objects;
7+
8+
/// A compiled json-transforms program.
9+
///
10+
/// This implementation follows the Microsoft json-document-transforms specification (wiki):
11+
/// `https://github.com/Microsoft/json-document-transforms/wiki`
12+
///
13+
/// Parse/compile once via `parse(...)`, then apply to many source documents via `run(...)`.
14+
public final class JsonTransform {
15+
16+
private final TransformAst.ObjectTransform root;
17+
18+
private JsonTransform(TransformAst.ObjectTransform root) {
19+
this.root = root;
20+
}
21+
22+
/// Parses (compiles) a transform JSON value into a reusable program.
23+
/// @param transform the transform JSON (must be an object)
24+
/// @return a compiled, reusable transform program
25+
public static JsonTransform parse(JsonValue transform) {
26+
Objects.requireNonNull(transform, "transform must not be null");
27+
if (!(transform instanceof JsonObject obj)) {
28+
throw new JsonTransformException("transform must be a JSON object, got: " + transform.getClass().getSimpleName());
29+
}
30+
return new JsonTransform(TransformCompiler.compileObject(obj));
31+
}
32+
33+
/// Applies this transform to a source JSON value.
34+
///
35+
/// The source must be a JSON object, matching the reference implementation expectation.
36+
///
37+
/// @param source the source JSON value (must be an object)
38+
/// @return the transformed JSON value
39+
public JsonValue run(JsonValue source) {
40+
Objects.requireNonNull(source, "source must not be null");
41+
if (!(source instanceof JsonObject obj)) {
42+
throw new JsonTransformException("source must be a JSON object, got: " + source.getClass().getSimpleName());
43+
}
44+
return TransformRunner.applyAtDocumentRoot(obj, root);
45+
}
46+
}
47+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package json.java21.transforms;
2+
3+
/// Exception thrown for invalid transform syntax or runtime failures applying a transform.
4+
public final class JsonTransformException extends RuntimeException {
5+
public JsonTransformException(String message) {
6+
super(message);
7+
}
8+
9+
public JsonTransformException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
}
13+
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package json.java21.transforms;
2+
3+
import json.java21.jsonpath.JsonPath;
4+
import jdk.sandbox.java.util.json.JsonObject;
5+
import jdk.sandbox.java.util.json.JsonValue;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
11+
sealed interface TransformAst {
12+
13+
record ObjectTransform(
14+
Map<String, JsonValue> nonVerbMembers,
15+
Map<String, ObjectTransform> childObjects,
16+
List<RemoveOp> removes,
17+
List<ReplaceOp> replaces,
18+
List<MergeOp> merges,
19+
List<RenameOp> renames
20+
) implements TransformAst {
21+
ObjectTransform {
22+
Objects.requireNonNull(nonVerbMembers, "nonVerbMembers must not be null");
23+
Objects.requireNonNull(childObjects, "childObjects must not be null");
24+
Objects.requireNonNull(removes, "removes must not be null");
25+
Objects.requireNonNull(replaces, "replaces must not be null");
26+
Objects.requireNonNull(merges, "merges must not be null");
27+
Objects.requireNonNull(renames, "renames must not be null");
28+
nonVerbMembers = Map.copyOf(nonVerbMembers);
29+
childObjects = Map.copyOf(childObjects);
30+
removes = List.copyOf(removes);
31+
replaces = List.copyOf(replaces);
32+
merges = List.copyOf(merges);
33+
renames = List.copyOf(renames);
34+
}
35+
}
36+
37+
sealed interface RemoveOp extends TransformAst permits RemoveOp.ByName, RemoveOp.RemoveThis, RemoveOp.ByPath {
38+
record ByName(String name) implements RemoveOp {
39+
ByName {
40+
Objects.requireNonNull(name, "name must not be null");
41+
}
42+
}
43+
44+
record RemoveThis() implements RemoveOp {}
45+
46+
record ByPath(String rawPath, JsonPath path) implements RemoveOp {
47+
ByPath {
48+
Objects.requireNonNull(rawPath, "rawPath must not be null");
49+
Objects.requireNonNull(path, "path must not be null");
50+
}
51+
}
52+
}
53+
54+
sealed interface ReplaceOp extends TransformAst permits ReplaceOp.ReplaceThis, ReplaceOp.ByPath {
55+
record ReplaceThis(JsonValue value) implements ReplaceOp {
56+
ReplaceThis {
57+
Objects.requireNonNull(value, "value must not be null");
58+
}
59+
}
60+
61+
record ByPath(String rawPath, JsonPath path, JsonValue value) implements ReplaceOp {
62+
ByPath {
63+
Objects.requireNonNull(rawPath, "rawPath must not be null");
64+
Objects.requireNonNull(path, "path must not be null");
65+
Objects.requireNonNull(value, "value must not be null");
66+
}
67+
}
68+
}
69+
70+
sealed interface MergeOp extends TransformAst permits MergeOp.MergeThis, MergeOp.ByPath {
71+
72+
record MergeThis(Value value) implements MergeOp {
73+
MergeThis {
74+
Objects.requireNonNull(value, "value must not be null");
75+
}
76+
}
77+
78+
record ByPath(String rawPath, JsonPath path, Value value) implements MergeOp {
79+
ByPath {
80+
Objects.requireNonNull(rawPath, "rawPath must not be null");
81+
Objects.requireNonNull(path, "path must not be null");
82+
Objects.requireNonNull(value, "value must not be null");
83+
}
84+
}
85+
86+
sealed interface Value permits Value.Raw, Value.TransformObjectValue {
87+
record Raw(JsonValue value) implements Value {
88+
Raw {
89+
Objects.requireNonNull(value, "value must not be null");
90+
}
91+
}
92+
93+
record TransformObjectValue(JsonObject rawObject, ObjectTransform compiled) implements Value {
94+
TransformObjectValue {
95+
Objects.requireNonNull(rawObject, "rawObject must not be null");
96+
Objects.requireNonNull(compiled, "compiled must not be null");
97+
}
98+
}
99+
}
100+
}
101+
102+
sealed interface RenameOp extends TransformAst permits RenameOp.Mapping, RenameOp.ByPath {
103+
record Mapping(Map<String, String> renames) implements RenameOp {
104+
Mapping {
105+
Objects.requireNonNull(renames, "renames must not be null");
106+
renames = Map.copyOf(renames);
107+
}
108+
}
109+
110+
record ByPath(String rawPath, JsonPath path, String newName) implements RenameOp {
111+
ByPath {
112+
Objects.requireNonNull(rawPath, "rawPath must not be null");
113+
Objects.requireNonNull(path, "path must not be null");
114+
Objects.requireNonNull(newName, "newName must not be null");
115+
}
116+
}
117+
}
118+
}
119+

0 commit comments

Comments
 (0)