();
+
+ /**
+ * Creates a {@code JSONPointer} instance using the tokens previously set using the
+ * {@link #append(String)} method calls.
+ * @return a JSONPointer object
+ */
+ public JSONPointer build() {
+ return new JSONPointer(this.refTokens);
+ }
+
+ /**
+ * Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
+ *
+ * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
+ * argument of this method MUST NOT be escaped. If you want to query the property called
+ * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no
+ * need to escape it as {@code "a~0b"}.
+ *
+ * @param token the new token to be appended to the list
+ * @return {@code this}
+ * @throws NullPointerException if {@code token} is null
+ */
+ public Builder append(String token) {
+ if (token == null) {
+ throw new NullPointerException("token cannot be null");
+ }
+ this.refTokens.add(token);
+ return this;
+ }
+
+ /**
+ * Adds an integer to the reference token list. Although not necessarily, mostly this token will
+ * denote an array index.
+ *
+ * @param arrayIndex the array index to be added to the token list
+ * @return {@code this}
+ */
+ public Builder append(int arrayIndex) {
+ this.refTokens.add(String.valueOf(arrayIndex));
+ return this;
+ }
+ }
+
+ /**
+ * Static factory method for {@link Builder}. Example usage:
+ *
+ *
+ * JSONPointer pointer = JSONPointer.builder()
+ * .append("obj")
+ * .append("other~key").append("another/key")
+ * .append("\"")
+ * .append(0)
+ * .build();
+ *
+ *
+ * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained
+ * {@link Builder#append(String)} calls.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ // Segments for the JSONPointer string
+ private final List refTokens;
+
+ /**
+ * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to
+ * evaluate the same JSON Pointer on different JSON documents then it is recommended
+ * to keep the {@code JSONPointer} instances due to performance considerations.
+ *
+ * @param pointer the JSON String or URI Fragment representation of the JSON pointer.
+ * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer
+ */
+ public JSONPointer(final String pointer) {
+ if (pointer == null) {
+ throw new NullPointerException("pointer cannot be null");
+ }
+ if (pointer.isEmpty() || "#".equals(pointer)) {
+ this.refTokens = Collections.emptyList();
+ return;
+ }
+ String refs;
+ if (pointer.startsWith("#/")) {
+ refs = pointer.substring(2);
+ try {
+ // URI fragments preserve plus signs rather than treating them as form-encoded spaces.
+ refs = URLDecoder.decode(refs.replace("+", "%2B"), ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ } else if (pointer.startsWith("/")) {
+ refs = pointer.substring(1);
+ } else {
+ throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
+ }
+ this.refTokens = new ArrayList();
+ int slashIdx = -1;
+ int prevSlashIdx = 0;
+ do {
+ prevSlashIdx = slashIdx + 1;
+ slashIdx = refs.indexOf('/', prevSlashIdx);
+ if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
+ // found 2 slashes in a row ( obj//next )
+ // or single slash at the end of a string ( obj/test/ )
+ this.refTokens.add("");
+ } else if (slashIdx >= 0) {
+ final String token = refs.substring(prevSlashIdx, slashIdx);
+ this.refTokens.add(unescape(token));
+ } else {
+ // last item after separator, or no separator at all.
+ final String token = refs.substring(prevSlashIdx);
+ this.refTokens.add(unescape(token));
+ }
+ } while (slashIdx >= 0);
+ // using split does not take into account consecutive separators or "ending nulls"
+ //for (String token : refs.split("/")) {
+ // this.refTokens.add(unescape(token));
+ //}
+ }
+
+ /**
+ * Constructs a new JSONPointer instance with the provided list of reference tokens.
+ *
+ * @param refTokens A list of strings representing the reference tokens for the JSON Pointer.
+ * Each token identifies a step in the path to the targeted value.
+ */
+ public JSONPointer(List refTokens) {
+ this.refTokens = new ArrayList(refTokens);
+ }
+
+ /**
+ * @see rfc6901 section 3
+ */
+ private static String unescape(String token) {
+ return token.replace("~1", "/").replace("~0", "~");
+ }
+
+ /**
+ * Evaluates this JSON Pointer on the given {@code document}. The {@code document}
+ * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty
+ * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the
+ * returned value will be {@code document} itself.
+ *
+ * @param document the JSON document which should be the subject of querying.
+ * @return the result of the evaluation
+ * @throws JSONPointerException if an error occurs during evaluation
+ */
+ public Object queryFrom(Object document) throws JSONPointerException {
+ if (this.refTokens.isEmpty()) {
+ return document;
+ }
+ Object current = document;
+ for (String token : this.refTokens) {
+ if (current instanceof JSONObject) {
+ current = ((JSONObject) current).opt(token);
+ } else if (current instanceof JSONArray) {
+ current = readByIndexToken(current, token);
+ } else {
+ throw new JSONPointerException(format(
+ "value [%s] is not an array or object therefore its key %s cannot be resolved", current,
+ token));
+ }
+ }
+ return current;
+ }
+
+ /**
+ * Matches a JSONArray element by ordinal position
+ * @param current the JSONArray to be evaluated
+ * @param indexToken the array index in string form
+ * @return the matched object. If no matching item is found a
+ * @throws JSONPointerException is thrown if the index is out of bounds
+ */
+ private static Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
+ try {
+ int index = Integer.parseInt(indexToken);
+ JSONArray currentArr = (JSONArray) current;
+ if (index >= currentArr.length()) {
+ throw new JSONPointerException(format("index %s is out of bounds - the array has %d elements", indexToken,
+ Integer.valueOf(currentArr.length())));
+ }
+ try {
+ return currentArr.get(index);
+ } catch (JSONException e) {
+ throw new JSONPointerException("Error reading value at index position " + index, e);
+ }
+ } catch (NumberFormatException e) {
+ throw new JSONPointerException(format("%s is not an array index", indexToken), e);
+ }
+ }
+
+ /**
+ * Returns a string representing the JSONPointer path value using string
+ * representation
+ */
+ @Override
+ public String toString() {
+ StringBuilder rval = new StringBuilder();
+ for (String token: this.refTokens) {
+ rval.append('/').append(escape(token));
+ }
+ return rval.toString();
+ }
+
+ /**
+ * Escapes path segment values to an unambiguous form.
+ * The escape char to be inserted is '~'. The chars to be escaped
+ * are ~, which maps to ~0, and /, which maps to ~1.
+ * @param token the JSONPointer segment value to be escaped
+ * @return the escaped value for the token
+ *
+ * @see rfc6901 section 3
+ */
+ private static String escape(String token) {
+ return token.replace("~", "~0")
+ .replace("/", "~1");
+ }
+
+ /**
+ * Returns a string representing the JSONPointer path value using URI
+ * fragment identifier representation
+ * @return a uri fragment string
+ */
+ public String toURIFragment() {
+ try {
+ StringBuilder rval = new StringBuilder("#");
+ for (String token : this.refTokens) {
+ rval.append('/').append(URLEncoder.encode(escape(token), ENCODING).replace("+", "%20"));
+ }
+ return rval.toString();
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/test/java/org/json/junit/JSONPointerTest.java b/src/test/java/org/json/junit/JSONPointerTest.java
index 7095d45da..9275efab0 100644
--- a/src/test/java/org/json/junit/JSONPointerTest.java
+++ b/src/test/java/org/json/junit/JSONPointerTest.java
@@ -1,444 +1,479 @@
-package org.json.junit;
-
-/*
-Public Domain.
-*/
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.InputStream;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.json.JSONPointer;
-import org.json.JSONPointerException;
-import org.json.JSONTokener;
-import org.junit.Test;
-
-public class JSONPointerTest {
-
- private static final JSONObject document;
- private static final String EXPECTED_COMPLETE_DOCUMENT = "{\"\":0,\" \":7,\"g|h\":4,\"c%d\":2,\"k\\\"l\":6,\"a/b\":1,\"i\\\\j\":5," +
- "\"obj\":{\"\":{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some other value\"}," +
- "\"other~key\":{\"another/key\":[\"val\"]},\"key\":\"value\"},\"foo\":[\"bar\",\"baz\"],\"e^f\":3," +
- "\"m~n\":8}";
-
-
- static {
- @SuppressWarnings("resource")
- InputStream resourceAsStream = JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json");
- if(resourceAsStream == null) {
- throw new ExceptionInInitializerError("Unable to locate test file. Please check your development environment configuration");
- }
- document = new JSONObject(new JSONTokener(resourceAsStream));
- }
-
- private Object query(String pointer) {
- return new JSONPointer(pointer).queryFrom(document);
- }
-
- @Test
- public void emptyPointer() {
- assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("")));
- }
-
- @SuppressWarnings("unused")
- @Test(expected = NullPointerException.class)
- public void nullPointer() {
- new JSONPointer((String) null);
- }
-
- @Test
- public void objectPropertyQuery() {
- assertEquals("[\"bar\",\"baz\"]", query("/foo").toString());
- }
-
- @Test
- public void arrayIndexQuery() {
- assertEquals("bar", query("/foo/0"));
- }
-
- @Test(expected = JSONPointerException.class)
- public void stringPropOfArrayFailure() {
- query("/foo/bar");
- }
-
- @Test
- public void queryByEmptyKey() {
- assertEquals(0, query("/"));
- }
-
- @Test
- public void queryByEmptyKeySubObject() {
- JSONObject json = new JSONObject("{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" +
- " other value\"}");
- JSONObject obj = (JSONObject) query("/obj/");
- assertTrue(json.similar(obj));
- }
-
- @Test
- public void queryByEmptyKeySubObjectSubOject() {
- assertEquals("empty key of an object with an empty key", query("/obj//"));
- }
-
- @Test
- public void queryByEmptyKeySubObjectValue() {
- assertEquals("Some other value", query("/obj//subKey"));
- }
-
- @Test
- public void slashEscaping() {
- assertEquals(1, query("/a~1b"));
- }
-
- @Test
- public void tildeEscaping() {
- assertEquals(8, query("/m~0n"));
- }
-
- @Test
- public void escapeSequencesInObjectKeys() {
- JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash")
- .put("~0", "tilde zero").put("~", "tilde");
- assertEquals("tilde one", object.query("/~01"));
- assertEquals("tilde zero", object.query("/~00"));
- assertEquals("tilde one", object.query("#/~01"));
- assertEquals("tilde zero", object.query("#/~00"));
- }
-
- @Test
- public void builderPreservesLiteralTokens() {
- JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash")
- .put("~0", "tilde zero").put("~", "tilde");
- assertEquals("tilde one", JSONPointer.builder().append("~1").build().queryFrom(object));
- assertEquals("tilde zero", JSONPointer.builder().append("~0").build().queryFrom(object));
- }
-
- @Test
- public void tokenListPreservesLiteralTokens() {
- JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash")
- .put("~0", "tilde zero").put("~", "tilde");
- assertEquals("tilde one", new JSONPointer(java.util.Arrays.asList("~1")).queryFrom(object));
- assertEquals("tilde zero", new JSONPointer(java.util.Arrays.asList("~0")).queryFrom(object));
- }
-
- /**
- * We pass backslashes as-is
- *
- * @see rfc6901 section 3
- */
- @Test
- public void backslashHandling() {
- assertEquals(5, query("/i\\j"));
- }
-
- /**
- * We pass quotations as-is
- *
- * @see rfc6901 section 3
- */
- @Test
- public void quotationHandling() {
- assertEquals(6, query("/k\"l"));
- }
-
- @Test
- public void whitespaceKey() {
- assertEquals(7, query("/ "));
- }
-
- @Test
- public void uriFragmentNotation() {
- assertEquals("[\"bar\",\"baz\"]", query("#/foo").toString());
- }
-
- @Test
- public void uriFragmentNotationRoot() {
- assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("#")));
- }
-
- @Test
- public void uriFragmentPercentHandling() {
- assertEquals(2, query("#/c%25d"));
- assertEquals(3, query("#/e%5Ef"));
- assertEquals(4, query("#/g%7Ch"));
- assertEquals(8, query("#/m~0n"));
- }
-
- @SuppressWarnings("unused")
- @Test(expected = IllegalArgumentException.class)
- public void syntaxError() {
- new JSONPointer("key");
- }
-
- @Test(expected = JSONPointerException.class)
- public void arrayIndexFailure() {
- query("/foo/2");
- }
-
- @Test(expected = JSONPointerException.class)
- public void primitiveFailure() {
- query("/obj/key/failure");
- }
-
- @Test
- public void builderTest() {
- JSONPointer pointer = JSONPointer.builder()
- .append("obj")
- .append("other~key").append("another/key")
- .append(0)
- .build();
- assertEquals("val", pointer.queryFrom(document));
- }
-
- @Test(expected = NullPointerException.class)
- public void nullToken() {
- JSONPointer.builder().append(null);
- }
-
- @Test
- public void toStringEscaping() {
- JSONPointer pointer = JSONPointer.builder()
- .append("obj")
- .append("other~key").append("another/key")
- .append("\"")
- .append(0)
- .build();
- assertEquals("/obj/other~0key/another~1key/\"/0", pointer.toString());
- }
-
- @Test
- public void emptyPointerToString() {
- assertEquals("", new JSONPointer("").toString());
- }
-
- @Test
- public void toURIFragment() {
- assertEquals("#/c%25d", new JSONPointer("/c%d").toURIFragment());
- assertEquals("#/e%5Ef", new JSONPointer("/e^f").toURIFragment());
- assertEquals("#/g%7Ch", new JSONPointer("/g|h").toURIFragment());
- assertEquals("#/m%7E0n", new JSONPointer("/m~n").toURIFragment());
- }
-
- @Test
- public void toURIFragmentEscaping() {
- JSONPointer pointer = JSONPointer.builder()
- .append("obj")
- .append("other~key").append("another/key")
- .append(0)
- .build();
- assertEquals("#/obj/other%7E0key/another%7E1key/0", pointer.toURIFragment());
- assertEquals("val", new JSONPointer(pointer.toURIFragment()).queryFrom(document));
- }
-
- @Test
- public void toURIFragmentRoundTrip() {
- JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash");
- JSONPointer tildeOne = JSONPointer.builder().append("~1").build();
- JSONPointer slash = JSONPointer.builder().append("/").build();
- assertEquals("tilde one", object.query(tildeOne.toURIFragment()));
- assertEquals("slash", object.query(slash.toURIFragment()));
- }
-
- @Test
- public void tokenListIsCopiedInConstructor() {
- JSONPointer.Builder b = JSONPointer.builder().append("key1");
- JSONPointer jp1 = b.build();
- b.append("key2");
- JSONPointer jp2 = b.build();
- if(jp1.toString().equals(jp2.toString())) {
- fail("Oops, my pointers are sharing a backing array");
- }
- }
-
- /**
- * Coverage for JSONObject query(String)
- */
- @Test
- public void queryFromJSONObject() {
- String str = "{"+
- "\"stringKey\":\"hello world!\","+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\": {"+
- "\"a\":\"aVal\","+
- "\"b\":\"bVal\""+
- "}"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- Object obj = jsonObject.query("/stringKey");
- assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
- obj = jsonObject.query("/arrayKey/1");
- assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
- obj = jsonObject.query("/objectKey/b");
- assertTrue("Expected bVal", "bVal".equals(obj));
- try {
- obj = jsonObject.query("/a/b/c");
- assertTrue("Expected JSONPointerException", false);
- } catch (JSONPointerException e) {
- assertTrue("Expected bad key/value exception",
- "value [null] is not an array or object therefore its key b cannot be resolved".
- equals(e.getMessage()));
- }
- }
-
- /**
- * Coverage for JSONObject query(JSONPointer)
- */
- @Test
- public void queryFromJSONObjectUsingPointer() {
- String str = "{"+
- "\"stringKey\":\"hello world!\","+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\": {"+
- "\"a\":\"aVal\","+
- "\"b\":\"bVal\""+
- "}"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- Object obj = jsonObject.query(new JSONPointer("/stringKey"));
- assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
- obj = jsonObject.query(new JSONPointer("/arrayKey/1"));
- assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
- obj = jsonObject.query(new JSONPointer("/objectKey/b"));
- assertTrue("Expected bVal", "bVal".equals(obj));
- try {
- obj = jsonObject.query(new JSONPointer("/a/b/c"));
- assertTrue("Expected JSONPointerException", false);
- } catch (JSONPointerException e) {
- assertTrue("Expected bad key/value exception",
- "value [null] is not an array or object therefore its key b cannot be resolved".
- equals(e.getMessage()));
- }
- }
-
- /**
- * Coverage for JSONObject optQuery(JSONPointer)
- */
- @Test
- public void optQueryFromJSONObjectUsingPointer() {
- String str = "{"+
- "\"stringKey\":\"hello world!\","+
- "\"arrayKey\":[0,1,2],"+
- "\"objectKey\": {"+
- "\"a\":\"aVal\","+
- "\"b\":\"bVal\""+
- "}"+
- "}";
- JSONObject jsonObject = new JSONObject(str);
- Object obj = jsonObject.optQuery(new JSONPointer("/stringKey"));
- assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
- obj = jsonObject.optQuery(new JSONPointer("/arrayKey/1"));
- assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
- obj = jsonObject.optQuery(new JSONPointer("/objectKey/b"));
- assertTrue("Expected bVal", "bVal".equals(obj));
- obj = jsonObject.optQuery(new JSONPointer("/a/b/c"));
- assertTrue("Expected null", obj == null);
- }
-
- /**
- * Coverage for JSONArray query(String)
- */
- @Test
- public void queryFromJSONArray() {
- String str = "["+
- "\"hello world!\","+
- "[0,1,2],"+
- "{"+
- "\"a\":\"aVal\","+
- "\"b\":\"bVal\""+
- "}"+
- "]";
- JSONArray jsonArray = new JSONArray(str);
- Object obj = jsonArray.query("/0");
- assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
- obj = jsonArray.query("/1/1");
- assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
- obj = jsonArray.query("/2/b");
- assertTrue("Expected bVal", "bVal".equals(obj));
- try {
- obj = jsonArray.query("/a/b/c");
- assertTrue("Expected JSONPointerException", false);
- } catch (JSONPointerException e) {
- assertTrue("Expected bad index exception",
- "a is not an array index".equals(e.getMessage()));
- }
- }
-
- /**
- * Coverage for JSONArray query(JSONPointer)
- */
- @Test
- public void queryFromJSONArrayUsingPointer() {
- String str = "["+
- "\"hello world!\","+
- "[0,1,2],"+
- "{"+
- "\"a\":\"aVal\","+
- "\"b\":\"bVal\""+
- "}"+
- "]";
- JSONArray jsonArray = new JSONArray(str);
- Object obj = jsonArray.query(new JSONPointer("/0"));
- assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
- obj = jsonArray.query(new JSONPointer("/1/1"));
- assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
- obj = jsonArray.query(new JSONPointer("/2/b"));
- assertTrue("Expected bVal", "bVal".equals(obj));
- try {
- obj = jsonArray.query(new JSONPointer("/a/b/c"));
- assertTrue("Expected JSONPointerException", false);
- } catch (JSONPointerException e) {
- assertTrue("Expected bad index exception",
- "a is not an array index".equals(e.getMessage()));
- }
- }
-
- /**
- * Coverage for JSONArray optQuery(JSONPointer)
- */
- @Test
- public void optQueryFromJSONArrayUsingPointer() {
- String str = "["+
- "\"hello world!\","+
- "[0,1,2],"+
- "{"+
- "\"a\":\"aVal\","+
- "\"b\":\"bVal\""+
- "}"+
- "]";
- JSONArray jsonArray = new JSONArray(str);
- Object obj = jsonArray.optQuery(new JSONPointer("/0"));
- assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
- obj = jsonArray.optQuery(new JSONPointer("/1/1"));
- assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
- obj = jsonArray.optQuery(new JSONPointer("/2/b"));
- assertTrue("Expected bVal", "bVal".equals(obj));
- obj = jsonArray.optQuery(new JSONPointer("/a/b/c"));
- assertTrue("Expected null", obj == null);
- }
-
- /**
- * When creating a jsonObject we need to parse escaped characters "\\\\"
- * --> it's the string representation of "\\", so when query'ing via the JSONPointer
- * we DON'T escape them
- *
- */
- @Test
- public void queryFromJSONObjectUsingPointer0() {
- String str = "{"+
- "\"string\\\\\\\\Key\":\"hello world!\","+
-
- "\"\\\\\":\"slash test\"" +
- "}";
- JSONObject jsonObject = new JSONObject(str);
- //Summary of issue: When a KEY in the jsonObject is "\\\\" --> it's held
- // as "\\" which means when querying, we need to use "\\"
- Object twoBackslahObj = jsonObject.optQuery(new JSONPointer("/\\"));
- assertEquals("slash test", twoBackslahObj);
-
- Object fourBackslashObj = jsonObject.optQuery(new JSONPointer("/string\\\\Key"));
- assertEquals("hello world!", fourBackslashObj);
- }
-}
+package org.json.junit;
+
+/*
+Public Domain.
+*/
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.InputStream;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.json.JSONPointer;
+import org.json.JSONPointerException;
+import org.json.JSONTokener;
+import org.junit.Test;
+
+public class JSONPointerTest {
+
+ private static final JSONObject document;
+ private static final String EXPECTED_COMPLETE_DOCUMENT = "{\"\":0,\" \":7,\"g|h\":4,\"c%d\":2,\"k\\\"l\":6,\"a/b\":1,\"i\\\\j\":5," +
+ "\"obj\":{\"\":{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some other value\"}," +
+ "\"other~key\":{\"another/key\":[\"val\"]},\"key\":\"value\"},\"foo\":[\"bar\",\"baz\"],\"e^f\":3," +
+ "\"m~n\":8}";
+
+
+ static {
+ @SuppressWarnings("resource")
+ InputStream resourceAsStream = JSONPointerTest.class.getClassLoader().getResourceAsStream("jsonpointer-testdoc.json");
+ if(resourceAsStream == null) {
+ throw new ExceptionInInitializerError("Unable to locate test file. Please check your development environment configuration");
+ }
+ document = new JSONObject(new JSONTokener(resourceAsStream));
+ }
+
+ private Object query(String pointer) {
+ return new JSONPointer(pointer).queryFrom(document);
+ }
+
+ @Test
+ public void emptyPointer() {
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("")));
+ }
+
+ @SuppressWarnings("unused")
+ @Test(expected = NullPointerException.class)
+ public void nullPointer() {
+ new JSONPointer((String) null);
+ }
+
+ @Test
+ public void objectPropertyQuery() {
+ assertEquals("[\"bar\",\"baz\"]", query("/foo").toString());
+ }
+
+ @Test
+ public void arrayIndexQuery() {
+ assertEquals("bar", query("/foo/0"));
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void stringPropOfArrayFailure() {
+ query("/foo/bar");
+ }
+
+ @Test
+ public void queryByEmptyKey() {
+ assertEquals(0, query("/"));
+ }
+
+ @Test
+ public void queryByEmptyKeySubObject() {
+ JSONObject json = new JSONObject("{\"\":\"empty key of an object with an empty key\",\"subKey\":\"Some" +
+ " other value\"}");
+ JSONObject obj = (JSONObject) query("/obj/");
+ assertTrue(json.similar(obj));
+ }
+
+ @Test
+ public void queryByEmptyKeySubObjectSubOject() {
+ assertEquals("empty key of an object with an empty key", query("/obj//"));
+ }
+
+ @Test
+ public void queryByEmptyKeySubObjectValue() {
+ assertEquals("Some other value", query("/obj//subKey"));
+ }
+
+ @Test
+ public void slashEscaping() {
+ assertEquals(1, query("/a~1b"));
+ }
+
+ @Test
+ public void tildeEscaping() {
+ assertEquals(8, query("/m~0n"));
+ }
+
+ @Test
+ public void escapeSequencesInObjectKeys() {
+ JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash")
+ .put("~0", "tilde zero").put("~", "tilde");
+ assertEquals("tilde one", object.query("/~01"));
+ assertEquals("tilde zero", object.query("/~00"));
+ assertEquals("tilde one", object.query("#/~01"));
+ assertEquals("tilde zero", object.query("#/~00"));
+ }
+
+ @Test
+ public void builderPreservesLiteralTokens() {
+ JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash")
+ .put("~0", "tilde zero").put("~", "tilde");
+ assertEquals("tilde one", JSONPointer.builder().append("~1").build().queryFrom(object));
+ assertEquals("tilde zero", JSONPointer.builder().append("~0").build().queryFrom(object));
+ }
+
+ @Test
+ public void tokenListPreservesLiteralTokens() {
+ JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash")
+ .put("~0", "tilde zero").put("~", "tilde");
+ assertEquals("tilde one", new JSONPointer(java.util.Arrays.asList("~1")).queryFrom(object));
+ assertEquals("tilde zero", new JSONPointer(java.util.Arrays.asList("~0")).queryFrom(object));
+ }
+
+ /**
+ * We pass backslashes as-is
+ *
+ * @see rfc6901 section 3
+ */
+ @Test
+ public void backslashHandling() {
+ assertEquals(5, query("/i\\j"));
+ }
+
+ /**
+ * We pass quotations as-is
+ *
+ * @see rfc6901 section 3
+ */
+ @Test
+ public void quotationHandling() {
+ assertEquals(6, query("/k\"l"));
+ }
+
+ @Test
+ public void whitespaceKey() {
+ assertEquals(7, query("/ "));
+ }
+
+ @Test
+ public void uriFragmentNotation() {
+ assertEquals("[\"bar\",\"baz\"]", query("#/foo").toString());
+ }
+
+ @Test
+ public void uriFragmentNotationRoot() {
+ assertTrue(new JSONObject(EXPECTED_COMPLETE_DOCUMENT).similar(query("#")));
+ }
+
+ @Test
+ public void uriFragmentPercentHandling() {
+ assertEquals(2, query("#/c%25d"));
+ assertEquals(3, query("#/e%5Ef"));
+ assertEquals(4, query("#/g%7Ch"));
+ assertEquals(8, query("#/m~0n"));
+ }
+
+ @Test
+ public void uriFragmentPreservesLiteralPlus() {
+ JSONObject object = new JSONObject().put("a+b", "plus").put("a b", "space")
+ .put("a%2Bb", "percent");
+ assertEquals("plus", object.query("#/a+b"));
+ assertEquals("plus", object.query("#/a%2Bb"));
+ assertEquals("space", object.query("#/a%20b"));
+ assertEquals("percent", object.query("#/a%252Bb"));
+ assertEquals("plus", object.query("/a+b"));
+ }
+
+ @Test
+ public void uriFragmentEncodesSpaces() {
+ JSONObject object = new JSONObject().put("a+b", "plus").put("a b", "space");
+ JSONPointer plus = JSONPointer.builder().append("a+b").build();
+ JSONPointer space = JSONPointer.builder().append("a b").build();
+ assertEquals("#/a%2Bb", plus.toURIFragment());
+ assertEquals("#/a%20b", space.toURIFragment());
+ assertEquals("#/%20", new JSONPointer("/ ").toURIFragment());
+ assertEquals("plus", object.query(plus.toURIFragment()));
+ assertEquals("space", object.query(space.toURIFragment()));
+ }
+
+ @Test
+ public void uriFragmentPreservesUtf8AndPointerEscapes() {
+ String key = "\u4e2d +/~1";
+ JSONObject object = new JSONObject().put(key, "expected")
+ .put("\u4e2d /~1", "wrong space");
+ assertEquals("expected", object.query("#/%E4%B8%AD%20+~1~01"));
+ assertEquals("expected", object.query("#/%E4%B8%AD%20%2B%7E1%7E01"));
+ JSONPointer pointer = JSONPointer.builder().append(key).build();
+ assertEquals("#/%E4%B8%AD%20%2B%7E1%7E01", pointer.toURIFragment());
+ assertEquals("expected", object.query(pointer.toURIFragment()));
+ }
+
+ @SuppressWarnings("unused")
+ @Test(expected = IllegalArgumentException.class)
+ public void syntaxError() {
+ new JSONPointer("key");
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void arrayIndexFailure() {
+ query("/foo/2");
+ }
+
+ @Test(expected = JSONPointerException.class)
+ public void primitiveFailure() {
+ query("/obj/key/failure");
+ }
+
+ @Test
+ public void builderTest() {
+ JSONPointer pointer = JSONPointer.builder()
+ .append("obj")
+ .append("other~key").append("another/key")
+ .append(0)
+ .build();
+ assertEquals("val", pointer.queryFrom(document));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void nullToken() {
+ JSONPointer.builder().append(null);
+ }
+
+ @Test
+ public void toStringEscaping() {
+ JSONPointer pointer = JSONPointer.builder()
+ .append("obj")
+ .append("other~key").append("another/key")
+ .append("\"")
+ .append(0)
+ .build();
+ assertEquals("/obj/other~0key/another~1key/\"/0", pointer.toString());
+ }
+
+ @Test
+ public void emptyPointerToString() {
+ assertEquals("", new JSONPointer("").toString());
+ }
+
+ @Test
+ public void toURIFragment() {
+ assertEquals("#/c%25d", new JSONPointer("/c%d").toURIFragment());
+ assertEquals("#/e%5Ef", new JSONPointer("/e^f").toURIFragment());
+ assertEquals("#/g%7Ch", new JSONPointer("/g|h").toURIFragment());
+ assertEquals("#/m%7E0n", new JSONPointer("/m~n").toURIFragment());
+ }
+
+ @Test
+ public void toURIFragmentEscaping() {
+ JSONPointer pointer = JSONPointer.builder()
+ .append("obj")
+ .append("other~key").append("another/key")
+ .append(0)
+ .build();
+ assertEquals("#/obj/other%7E0key/another%7E1key/0", pointer.toURIFragment());
+ assertEquals("val", new JSONPointer(pointer.toURIFragment()).queryFrom(document));
+ }
+
+ @Test
+ public void toURIFragmentRoundTrip() {
+ JSONObject object = new JSONObject().put("~1", "tilde one").put("/", "slash");
+ JSONPointer tildeOne = JSONPointer.builder().append("~1").build();
+ JSONPointer slash = JSONPointer.builder().append("/").build();
+ assertEquals("tilde one", object.query(tildeOne.toURIFragment()));
+ assertEquals("slash", object.query(slash.toURIFragment()));
+ }
+
+ @Test
+ public void tokenListIsCopiedInConstructor() {
+ JSONPointer.Builder b = JSONPointer.builder().append("key1");
+ JSONPointer jp1 = b.build();
+ b.append("key2");
+ JSONPointer jp2 = b.build();
+ if(jp1.toString().equals(jp2.toString())) {
+ fail("Oops, my pointers are sharing a backing array");
+ }
+ }
+
+ /**
+ * Coverage for JSONObject query(String)
+ */
+ @Test
+ public void queryFromJSONObject() {
+ String str = "{"+
+ "\"stringKey\":\"hello world!\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\": {"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj = jsonObject.query("/stringKey");
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonObject.query("/arrayKey/1");
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonObject.query("/objectKey/b");
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonObject.query("/a/b/c");
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad key/value exception",
+ "value [null] is not an array or object therefore its key b cannot be resolved".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONObject query(JSONPointer)
+ */
+ @Test
+ public void queryFromJSONObjectUsingPointer() {
+ String str = "{"+
+ "\"stringKey\":\"hello world!\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\": {"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj = jsonObject.query(new JSONPointer("/stringKey"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonObject.query(new JSONPointer("/arrayKey/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonObject.query(new JSONPointer("/objectKey/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonObject.query(new JSONPointer("/a/b/c"));
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad key/value exception",
+ "value [null] is not an array or object therefore its key b cannot be resolved".
+ equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONObject optQuery(JSONPointer)
+ */
+ @Test
+ public void optQueryFromJSONObjectUsingPointer() {
+ String str = "{"+
+ "\"stringKey\":\"hello world!\","+
+ "\"arrayKey\":[0,1,2],"+
+ "\"objectKey\": {"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ Object obj = jsonObject.optQuery(new JSONPointer("/stringKey"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonObject.optQuery(new JSONPointer("/arrayKey/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonObject.optQuery(new JSONPointer("/objectKey/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ obj = jsonObject.optQuery(new JSONPointer("/a/b/c"));
+ assertTrue("Expected null", obj == null);
+ }
+
+ /**
+ * Coverage for JSONArray query(String)
+ */
+ @Test
+ public void queryFromJSONArray() {
+ String str = "["+
+ "\"hello world!\","+
+ "[0,1,2],"+
+ "{"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "]";
+ JSONArray jsonArray = new JSONArray(str);
+ Object obj = jsonArray.query("/0");
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonArray.query("/1/1");
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonArray.query("/2/b");
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonArray.query("/a/b/c");
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad index exception",
+ "a is not an array index".equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONArray query(JSONPointer)
+ */
+ @Test
+ public void queryFromJSONArrayUsingPointer() {
+ String str = "["+
+ "\"hello world!\","+
+ "[0,1,2],"+
+ "{"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "]";
+ JSONArray jsonArray = new JSONArray(str);
+ Object obj = jsonArray.query(new JSONPointer("/0"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonArray.query(new JSONPointer("/1/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonArray.query(new JSONPointer("/2/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ try {
+ obj = jsonArray.query(new JSONPointer("/a/b/c"));
+ assertTrue("Expected JSONPointerException", false);
+ } catch (JSONPointerException e) {
+ assertTrue("Expected bad index exception",
+ "a is not an array index".equals(e.getMessage()));
+ }
+ }
+
+ /**
+ * Coverage for JSONArray optQuery(JSONPointer)
+ */
+ @Test
+ public void optQueryFromJSONArrayUsingPointer() {
+ String str = "["+
+ "\"hello world!\","+
+ "[0,1,2],"+
+ "{"+
+ "\"a\":\"aVal\","+
+ "\"b\":\"bVal\""+
+ "}"+
+ "]";
+ JSONArray jsonArray = new JSONArray(str);
+ Object obj = jsonArray.optQuery(new JSONPointer("/0"));
+ assertTrue("Expected 'hello world!'", "hello world!".equals(obj));
+ obj = jsonArray.optQuery(new JSONPointer("/1/1"));
+ assertTrue("Expected 1", Integer.valueOf(1).equals(obj));
+ obj = jsonArray.optQuery(new JSONPointer("/2/b"));
+ assertTrue("Expected bVal", "bVal".equals(obj));
+ obj = jsonArray.optQuery(new JSONPointer("/a/b/c"));
+ assertTrue("Expected null", obj == null);
+ }
+
+ /**
+ * When creating a jsonObject we need to parse escaped characters "\\\\"
+ * --> it's the string representation of "\\", so when query'ing via the JSONPointer
+ * we DON'T escape them
+ *
+ */
+ @Test
+ public void queryFromJSONObjectUsingPointer0() {
+ String str = "{"+
+ "\"string\\\\\\\\Key\":\"hello world!\","+
+
+ "\"\\\\\":\"slash test\"" +
+ "}";
+ JSONObject jsonObject = new JSONObject(str);
+ //Summary of issue: When a KEY in the jsonObject is "\\\\" --> it's held
+ // as "\\" which means when querying, we need to use "\\"
+ Object twoBackslahObj = jsonObject.optQuery(new JSONPointer("/\\"));
+ assertEquals("slash test", twoBackslahObj);
+
+ Object fourBackslashObj = jsonObject.optQuery(new JSONPointer("/string\\\\Key"));
+ assertEquals("hello world!", fourBackslashObj);
+ }
+}