Skip to content

Commit 97d0f8c

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 97d0f8c

File tree

2 files changed

+126
-3
lines changed

2 files changed

+126
-3
lines changed

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

+92-3
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
2828

2929
package com.redislabs.modules.rejson;
3030

31+
import java.util.Objects;
32+
3133
/**
3234
* Path is a ReJSON path, representing a valid path into an object
3335
*/
3436
public class Path {
35-
36-
public static final Path ROOT_PATH = new Path(".");
37-
37+
38+
public static final Path ROOT_PATH = new Path(".");
39+
3840
private final String strPath;
3941

4042
public Path(final String strPath) {
@@ -60,6 +62,10 @@ public static Path of(final String strPath) {
6062
return new Path(strPath);
6163
}
6264

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

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)