diff --git a/src/main/java/com/redislabs/modules/rejson/Path.java b/src/main/java/com/redislabs/modules/rejson/Path.java index 0e2e2a3..d156347 100644 --- a/src/main/java/com/redislabs/modules/rejson/Path.java +++ b/src/main/java/com/redislabs/modules/rejson/Path.java @@ -28,13 +28,15 @@ package com.redislabs.modules.rejson; +import java.util.Objects; + /** * Path is a ReJSON path, representing a valid path into an object */ public class Path { - - public static final Path ROOT_PATH = new Path("."); - + + public static final Path ROOT_PATH = new Path("."); + private final String strPath; public Path(final String strPath) { @@ -60,6 +62,10 @@ public static Path of(final String strPath) { return new Path(strPath); } + public static Path ofJsonPointer(final String strPath) { + return new Path(parse(strPath)); + } + @Override public boolean equals(Object obj) { if (obj == null) return false; @@ -72,4 +78,87 @@ public boolean equals(Object obj) { public int hashCode() { return strPath.hashCode(); } + + private static String parse(String path) { + Objects.requireNonNull(path, "Json Pointer Path cannot be null."); + + if (path.isEmpty()) { + // "" means all document + return ROOT_PATH.toString(); + } + if (path.charAt(0) != '/') { + throw new IllegalArgumentException("Json Pointer Path must start with '/'."); + } + + final char[] ary = path.toCharArray(); + StringBuilder result = new StringBuilder(); + StringBuilder builder = new StringBuilder(); + boolean number = true; + char prev = '/'; + for (int i = 1; i < ary.length; i++) { + char c = ary[i]; + switch (c) { + case '~': + if (prev == '~') { + number = false; + builder.append('~'); + } + break; + case '/': + if (prev == '~') { + number = false; + builder.append('~'); + } + if (builder.length() > 0 && number) { + result.append(".[").append(builder).append("]"); + } else { + result.append(".[\"").append(builder).append("\"]"); + } + number = true; + builder.setLength(0); + break; + case '0': + if (prev == '~') { + number = false; + builder.append("~"); + } else { + builder.append(c); + } + break; + case '1': + if (prev == '~') { + number = false; + builder.append("/"); + } else { + builder.append(c); + } + break; + default: + if (prev == '~') { + number = false; + builder.append('~'); + } + if (c < '0' || c > '9') { + number = false; + } + builder.append(c); + break; + } + prev = c; + } + if (prev == '~') { + number = false; + builder.append("~"); + } + if (builder.length() > 0) { + if (number) { + result.append(".[").append(builder).append("]"); + } else { + result.append(".[\"").append(builder).append("\"]"); + } + } else if (prev == '/') { + result.append(".[\"").append(builder).append("\"]"); + } + return result.toString(); + } } diff --git a/src/test/java/com/redislabs/modules/rejson/PathTest.java b/src/test/java/com/redislabs/modules/rejson/PathTest.java index 1e30b17..1c1468e 100644 --- a/src/test/java/com/redislabs/modules/rejson/PathTest.java +++ b/src/test/java/com/redislabs/modules/rejson/PathTest.java @@ -31,4 +31,38 @@ public void testPathHashCode() { assertEquals(Path.of(".a.b.c").hashCode(), Path.of(".a.b.c").hashCode()); assertNotEquals(Path.of(".a.b.c").hashCode(), Path.of(".b.c").hashCode()); } + + @Test + public void testJsonPointer() { + assertEquals(Path.ofJsonPointer(""), Path.ROOT_PATH); + assertEquals(Path.ofJsonPointer("/"), Path.of(".[\"\"]")); + assertEquals(Path.ofJsonPointer("//0"), Path.of(".[\"\"].[0]")); + assertEquals(Path.ofJsonPointer("//"), Path.of(".[\"\"].[\"\"]")); + assertEquals(Path.ofJsonPointer("// "), Path.of(".[\"\"].[\" \"]")); + assertEquals(Path.ofJsonPointer("/a/b/c"), Path.of(".[\"a\"].[\"b\"].[\"c\"]")); + assertEquals(Path.ofJsonPointer("/a/0/c"), Path.of(".[\"a\"].[0].[\"c\"]")); + assertEquals(Path.ofJsonPointer("/a/0b/c"), Path.of(".[\"a\"].[\"0b\"].[\"c\"]")); + assertEquals(Path.ofJsonPointer("/ab/cd/1010"), Path.of(".[\"ab\"].[\"cd\"].[1010]")); + assertEquals(Path.ofJsonPointer("/a/b/c").hashCode(), Path.of(".[\"a\"].[\"b\"].[\"c\"]").hashCode()); + + // escape test + assertEquals(Path.ofJsonPointer("/a/~0"), Path.of(".[\"a\"].[\"~\"]")); + assertEquals(Path.ofJsonPointer("/a/~1"), Path.of(".[\"a\"].[\"/\"]")); + assertEquals(Path.ofJsonPointer("/a/~0/c"), Path.of(".[\"a\"].[\"~\"].[\"c\"]")); + assertEquals(Path.ofJsonPointer("/a/~1/c"), Path.of(".[\"a\"].[\"/\"].[\"c\"]")); + assertEquals(Path.ofJsonPointer("/a/~~/c"), Path.of(".[\"a\"].[\"~~\"].[\"c\"]")); + assertEquals(Path.ofJsonPointer("/~/~~~/~"), Path.of(".[\"~\"].[\"~~~\"].[\"~\"]")); + assertEquals(Path.ofJsonPointer("/~/~~~/~~"), Path.of(".[\"~\"].[\"~~~\"].[\"~~\"]")); + assertEquals(Path.ofJsonPointer("/~/~~~0/~~"), Path.of(".[\"~\"].[\"~~~\"].[\"~~\"]")); + assertEquals(Path.ofJsonPointer("/~/'.'/~~"), Path.of(".[\"~\"].[\"'.'\"].[\"~~\"]")); + + // json path escape test + assertEquals(Path.ofJsonPointer("/\t"), Path.of(".[\"\t\"]")); + assertEquals(Path.ofJsonPointer("/\u0074"), Path.of(".[\"\u0074\"]")); + assertEquals(Path.ofJsonPointer("/'"), Path.of(".[\"'\"]")); + assertEquals(Path.ofJsonPointer("/\'"), Path.of(".[\"\'\"]")); + assertEquals(Path.ofJsonPointer("/\""), Path.of(".[\"\"\"]")); + assertEquals(Path.ofJsonPointer("/\n"), Path.of(".[\"\n\"]")); + assertEquals(Path.ofJsonPointer("/\\"), Path.of(".[\"\\\"]")); + } }