From e18598cf6dca91571a9c7542db12a6b1b40a1710 Mon Sep 17 00:00:00 2001 From: Baoyi Chen Date: Mon, 28 Feb 2022 16:47:22 +0800 Subject: [PATCH] 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]") the limitation of JsonPointer path is /abc/0 always convert to .[\"abc\"].[0] and not .[\"abc\"].[\"0\"] if users want to use .abc.0, they should use Path.of(".abc.0") instead of Path.ofJosnPointer("/abc/0") --- .../redis/clients/jedis/json/JsonPointer.java | 92 +++++++++++++++++++ .../java/redis/clients/jedis/json/Path.java | 4 + .../java/redis/clients/jedis/json/Path2.java | 4 + .../clients/jedis/modules/json/Path2Test.java | 34 +++++++ .../clients/jedis/modules/json/PathTest.java | 34 +++++++ 5 files changed, 168 insertions(+) create mode 100644 src/main/java/redis/clients/jedis/json/JsonPointer.java diff --git a/src/main/java/redis/clients/jedis/json/JsonPointer.java b/src/main/java/redis/clients/jedis/json/JsonPointer.java new file mode 100644 index 0000000000..096cf887e5 --- /dev/null +++ b/src/main/java/redis/clients/jedis/json/JsonPointer.java @@ -0,0 +1,92 @@ +package redis.clients.jedis.json; + +import java.util.Objects; + +/** + * an RFC6901 implementation that convert json pointer path to json path. + */ +class JsonPointer { + + static String parse(String path, String rootPath) { + Objects.requireNonNull(path, "Json Pointer Path cannot be null."); + + if (path.isEmpty()) { + // "" means all document + return rootPath; + } + 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/main/java/redis/clients/jedis/json/Path.java b/src/main/java/redis/clients/jedis/json/Path.java index e361cab2ea..a7fd610659 100644 --- a/src/main/java/redis/clients/jedis/json/Path.java +++ b/src/main/java/redis/clients/jedis/json/Path.java @@ -21,6 +21,10 @@ public String toString() { public static Path of(final String strPath) { return new Path(strPath); } + + public static Path ofJsonPointer(final String strPath) { + return new Path(JsonPointer.parse(strPath, ROOT_PATH.strPath)); + } @Override public boolean equals(Object obj) { diff --git a/src/main/java/redis/clients/jedis/json/Path2.java b/src/main/java/redis/clients/jedis/json/Path2.java index 28211bb470..2048c59ed6 100644 --- a/src/main/java/redis/clients/jedis/json/Path2.java +++ b/src/main/java/redis/clients/jedis/json/Path2.java @@ -33,6 +33,10 @@ public String toString() { public static Path2 of(final String path) { return new Path2(path); } + + public static Path2 ofJsonPointer(final String path) { + return new Path2(JsonPointer.parse(path, ROOT_PATH.str)); + } @Override public boolean equals(Object obj) { diff --git a/src/test/java/redis/clients/jedis/modules/json/Path2Test.java b/src/test/java/redis/clients/jedis/modules/json/Path2Test.java index ce27749cc6..d110543a4a 100644 --- a/src/test/java/redis/clients/jedis/modules/json/Path2Test.java +++ b/src/test/java/redis/clients/jedis/modules/json/Path2Test.java @@ -40,4 +40,38 @@ public void equals() { assertTrue(new Path2("a.b").equals(Path2.of(".a.b"))); assertTrue(Path2.of("a.b").equals(new Path2(".a.b"))); } + + @Test + public void testJsonPointer() { + assertEquals(Path2.ofJsonPointer(""), Path2.ROOT_PATH); + assertEquals(Path2.ofJsonPointer("/"), Path2.of("$.[\"\"]")); + assertEquals(Path2.ofJsonPointer("//0"), Path2.of("$.[\"\"].[0]")); + assertEquals(Path2.ofJsonPointer("//"), Path2.of("$.[\"\"].[\"\"]")); + assertEquals(Path2.ofJsonPointer("// "), Path2.of("$.[\"\"].[\" \"]")); + assertEquals(Path2.ofJsonPointer("/a/b/c"), Path2.of("$.[\"a\"].[\"b\"].[\"c\"]")); + assertEquals(Path2.ofJsonPointer("/a/0/c"), Path2.of("$.[\"a\"].[0].[\"c\"]")); + assertEquals(Path2.ofJsonPointer("/a/0b/c"), Path2.of("$.[\"a\"].[\"0b\"].[\"c\"]")); + assertEquals(Path2.ofJsonPointer("/ab/cd/1010"), Path2.of("$.[\"ab\"].[\"cd\"].[1010]")); + assertEquals(Path2.ofJsonPointer("/a/b/c").hashCode(), Path2.of("$.[\"a\"].[\"b\"].[\"c\"]").hashCode()); + + // escape test + assertEquals(Path2.ofJsonPointer("/a/~0"), Path2.of("$.[\"a\"].[\"~\"]")); + assertEquals(Path2.ofJsonPointer("/a/~1"), Path2.of("$.[\"a\"].[\"/\"]")); + assertEquals(Path2.ofJsonPointer("/a/~0/c"), Path2.of("$.[\"a\"].[\"~\"].[\"c\"]")); + assertEquals(Path2.ofJsonPointer("/a/~1/c"), Path2.of("$.[\"a\"].[\"/\"].[\"c\"]")); + assertEquals(Path2.ofJsonPointer("/a/~~/c"), Path2.of("$.[\"a\"].[\"~~\"].[\"c\"]")); + assertEquals(Path2.ofJsonPointer("/~/~~~/~"), Path2.of("$.[\"~\"].[\"~~~\"].[\"~\"]")); + assertEquals(Path2.ofJsonPointer("/~/~~~/~~"), Path2.of("$.[\"~\"].[\"~~~\"].[\"~~\"]")); + assertEquals(Path2.ofJsonPointer("/~/~~~0/~~"), Path2.of("$.[\"~\"].[\"~~~\"].[\"~~\"]")); + assertEquals(Path2.ofJsonPointer("/~/'.'/~~"), Path2.of("$.[\"~\"].[\"'.'\"].[\"~~\"]")); + + // json path escape test + assertEquals(Path2.ofJsonPointer("/\t"), Path2.of("$.[\"\t\"]")); + assertEquals(Path2.ofJsonPointer("/\u0074"), Path2.of("$.[\"\u0074\"]")); + assertEquals(Path2.ofJsonPointer("/'"), Path2.of("$.[\"'\"]")); + assertEquals(Path2.ofJsonPointer("/\'"), Path2.of("$.[\"\'\"]")); + assertEquals(Path2.ofJsonPointer("/\""), Path2.of("$.[\"\"\"]")); + assertEquals(Path2.ofJsonPointer("/\n"), Path2.of("$.[\"\n\"]")); + assertEquals(Path2.ofJsonPointer("/\\"), Path2.of("$.[\"\\\"]")); + } } diff --git a/src/test/java/redis/clients/jedis/modules/json/PathTest.java b/src/test/java/redis/clients/jedis/modules/json/PathTest.java index 15e014f226..d5194e530f 100644 --- a/src/test/java/redis/clients/jedis/modules/json/PathTest.java +++ b/src/test/java/redis/clients/jedis/modules/json/PathTest.java @@ -33,4 +33,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(".[\"\\\"]")); + } }