Skip to content

Commit c1d65d6

Browse files
committed
Add RFC6901 json pointer support
now Path can convert RFC6901 style path to json path for example Path.ofJosnPointer("/abc/0") can convert to Path.of(".abc.[0]")
1 parent 4858eea commit c1d65d6

File tree

2 files changed

+132
-3
lines changed

2 files changed

+132
-3
lines changed

src/main/java/com/redislabs/modules/rejson/Path.java

+98-3
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
* Path is a ReJSON path, representing a valid path into an object
3333
*/
3434
public class Path {
35-
36-
public static final Path ROOT_PATH = new Path(".");
37-
35+
36+
public static final Path ROOT_PATH = new Path(".");
37+
3838
private final String strPath;
3939

4040
public Path(final String strPath) {
@@ -60,6 +60,10 @@ public static Path of(final String strPath) {
6060
return new Path(strPath);
6161
}
6262

63+
public static Path ofJsonPointer(final String strPath) {
64+
return new Path(parse(strPath));
65+
}
66+
6367
@Override
6468
public boolean equals(Object obj) {
6569
if (obj == null) return false;
@@ -72,4 +76,95 @@ public boolean equals(Object obj) {
7276
public int hashCode() {
7377
return strPath.hashCode();
7478
}
79+
80+
private static String escape(String path) {
81+
StringBuilder builder = new StringBuilder();
82+
builder.append('"');
83+
builder.append(path);
84+
builder.append('"');
85+
return builder.toString();
86+
}
87+
88+
private static String parse(String path) {
89+
if (path == null) {
90+
throw new NullPointerException("Json Pointer Path cannot be null.");
91+
}
92+
if (path.isEmpty()) {
93+
// ”“ means all document
94+
return ROOT_PATH.toString();
95+
}
96+
if (path.charAt(0) != '/') {
97+
throw new IllegalArgumentException("Json Pointer Path must start with '/'.");
98+
}
99+
char[] ary = path.toCharArray();
100+
StringBuilder r = new StringBuilder(ary.length);
101+
StringBuilder builder = new StringBuilder();
102+
boolean num = true;
103+
char prev = '/';
104+
for (int i = 1; i < ary.length; i++) {
105+
char c = ary[i];
106+
switch (c) {
107+
case '~':
108+
if (prev == '~') {
109+
num = false;
110+
builder.append('~');
111+
}
112+
break;
113+
case '/':
114+
if (prev == '~') {
115+
num = false;
116+
builder.append('~');
117+
}
118+
if (builder.length() > 0 && num) {
119+
r.append(".[" + builder.toString() + "]");
120+
} else {
121+
r.append(".[" + escape(builder.toString()) + "]");
122+
}
123+
num = true;
124+
builder.setLength(0);
125+
break;
126+
case '0':
127+
if (prev == '~') {
128+
num = false;
129+
builder.append("~");
130+
} else {
131+
builder.append(c);
132+
}
133+
break;
134+
case '1':
135+
if (prev == '~') {
136+
num = false;
137+
builder.append("/");
138+
} else {
139+
builder.append(c);
140+
}
141+
break;
142+
default:
143+
if (prev == '~') {
144+
num = false;
145+
builder.append('~');
146+
}
147+
if (c < '0' || c > '9') {
148+
num = false;
149+
}
150+
builder.append(c);
151+
break;
152+
}
153+
prev = c;
154+
}
155+
if (prev == '~') {
156+
num = false;
157+
builder.append("~");
158+
}
159+
if (builder.length() > 0) {
160+
if (num) {
161+
r.append(".[" + builder.toString() + "]");
162+
} else {
163+
r.append(".[" + escape(builder.toString()) + "]");
164+
}
165+
} else if (prev == '/') {
166+
r.append(".[" + escape(builder.toString()) + "]");
167+
}
168+
return r.toString();
169+
}
75170
}

src/test/java/com/redislabs/modules/rejson/PathTest.java

+34
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,38 @@ public void testPathHashCode() {
3131
assertEquals(Path.of(".a.b.c").hashCode(), Path.of(".a.b.c").hashCode());
3232
assertNotEquals(Path.of(".a.b.c").hashCode(), Path.of(".b.c").hashCode());
3333
}
34+
35+
@Test
36+
public void testJsonPointer() {
37+
assertEquals(Path.ofJsonPointer(""), Path.ROOT_PATH);
38+
assertEquals(Path.ofJsonPointer("/"), Path.of(".[\"\"]"));
39+
assertEquals(Path.ofJsonPointer("//0"), Path.of(".[\"\"].[0]"));
40+
assertEquals(Path.ofJsonPointer("//"), Path.of(".[\"\"].[\"\"]"));
41+
assertEquals(Path.ofJsonPointer("// "), Path.of(".[\"\"].[\" \"]"));
42+
assertEquals(Path.ofJsonPointer("/a/b/c"), Path.of(".[\"a\"].[\"b\"].[\"c\"]"));
43+
assertEquals(Path.ofJsonPointer("/a/0/c"), Path.of(".[\"a\"].[0].[\"c\"]"));
44+
assertEquals(Path.ofJsonPointer("/a/0b/c"), Path.of(".[\"a\"].[\"0b\"].[\"c\"]"));
45+
assertEquals(Path.ofJsonPointer("/ab/cd/1010"), Path.of(".[\"ab\"].[\"cd\"].[1010]"));
46+
assertEquals(Path.ofJsonPointer("/a/b/c").hashCode(), Path.of(".[\"a\"].[\"b\"].[\"c\"]").hashCode());
47+
48+
// escape test
49+
assertEquals(Path.ofJsonPointer("/a/~0"), Path.of(".[\"a\"].[\"~\"]"));
50+
assertEquals(Path.ofJsonPointer("/a/~1"), Path.of(".[\"a\"].[\"/\"]"));
51+
assertEquals(Path.ofJsonPointer("/a/~0/c"), Path.of(".[\"a\"].[\"~\"].[\"c\"]"));
52+
assertEquals(Path.ofJsonPointer("/a/~1/c"), Path.of(".[\"a\"].[\"/\"].[\"c\"]"));
53+
assertEquals(Path.ofJsonPointer("/a/~~/c"), Path.of(".[\"a\"].[\"~~\"].[\"c\"]"));
54+
assertEquals(Path.ofJsonPointer("/~/~~~/~"), Path.of(".[\"~\"].[\"~~~\"].[\"~\"]"));
55+
assertEquals(Path.ofJsonPointer("/~/~~~/~~"), Path.of(".[\"~\"].[\"~~~\"].[\"~~\"]"));
56+
assertEquals(Path.ofJsonPointer("/~/~~~0/~~"), Path.of(".[\"~\"].[\"~~~\"].[\"~~\"]"));
57+
assertEquals(Path.ofJsonPointer("/~/'.'/~~"), Path.of(".[\"~\"].[\"'.'\"].[\"~~\"]"));
58+
59+
// json path escape test
60+
assertEquals(Path.ofJsonPointer("/\t"), Path.of(".[\"\t\"]"));
61+
assertEquals(Path.ofJsonPointer("/\u0074"), Path.of(".[\"\u0074\"]"));
62+
assertEquals(Path.ofJsonPointer("/'"), Path.of(".[\"'\"]"));
63+
assertEquals(Path.ofJsonPointer("/\'"), Path.of(".[\"\'\"]"));
64+
assertEquals(Path.ofJsonPointer("/\""), Path.of(".[\"\"\"]"));
65+
assertEquals(Path.ofJsonPointer("/\n"), Path.of(".[\"\n\"]"));
66+
assertEquals(Path.ofJsonPointer("/\\"), Path.of(".[\"\\\"]"));
67+
}
3468
}

0 commit comments

Comments
 (0)