20 Commits

Author SHA1 Message Date
Sean Leary
7299b201f4 Update pom.xml 2021-03-07 21:11:48 -06:00
Sean Leary
8cc1e9830d Update README.md 2021-03-07 21:09:01 -06:00
Sean Leary
c43e21ae73 Merge pull request #588 from fossterer/563-jsonpointer-do-not-encode-quotes
JSONPointer should not process reverse solidus or double-quote chars in tokens
2021-03-06 10:00:32 -06:00
Shashank Sabniveesu
d6ccc64c79 Closes 563: As never defined in RFC 6901 Section 3, do not handle backslashes (\) and quotes(") as anything special 2021-02-28 16:03:14 -05:00
Sean Leary
7844eb79cd Merge pull request #583 from ek08/fix
Checked the length of key for checker framework
2021-02-01 19:48:06 -06:00
Ehtesham
5b531faa49 Improved the logic for checking the length of key 2021-01-28 15:31:23 +05:30
Ehtesham
31ff8a2291 Checked the length of key for checker framework 2021-01-27 11:35:38 +05:30
Sean Leary
e33f463179 Merge pull request #581 from valfirst/patch-1
Use built-in Gradle shorthand notation for Maven Central repository
2021-01-10 18:35:31 -06:00
Valery Yatsynovich
e77a77e841 Use built-in Gradle shorthand notation for Maven Central repository 2020-12-29 14:16:46 +03:00
Sean Leary
26f48484fd Merge pull request #577 from stranck/master
Added clear() methods to JSONObject and JSONArray
2020-12-07 16:54:18 -06:00
Stranck
efad1d73a7 Added UnitTests
(I hope they works :c)
2020-12-04 04:09:19 +01:00
Stranck
c7130d577a Oops 2020-12-04 01:09:18 +01:00
Stranck
d85eea53bb Update JSONArray.java 2020-12-04 01:07:29 +01:00
Stranck
57ad94ef5e Added clear() methods to JSONObject and JSONArray 2020-12-04 00:49:21 +01:00
Sean Leary
a57eff26d5 Merge pull request #575 from johnjaylward/fix-similar-compare-numbers
Fix similar compare numbers
2020-11-22 15:23:03 -06:00
John J. Aylward
68883b9ff8 update number handling to use new helper method for consistency. 2020-11-19 19:10:08 -05:00
John J. Aylward
11e6b1af7e fixes issue #573 by added specific compare of numeric types 2020-11-19 18:55:49 -05:00
John J. Aylward
e4b76d6588 Add test to demonstrate the issue. See #573 2020-11-19 18:18:27 -05:00
John J. Aylward
3a8193bea4 upgrade junit version 2020-11-19 18:18:02 -05:00
Sean Leary
6bf2692a94 Update README.md 2020-11-15 16:09:50 -06:00
9 changed files with 194 additions and 91 deletions

View File

@@ -3,7 +3,7 @@ JSON in Java [package org.json]
[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json)
**[Click here if you just want the latest release jar file.](https://repo1.maven.org/maven2/org/json/json/20200518/json-20200518.jar)**
**[Click here if you just want the latest release jar file.](https://repo1.maven.org/maven2/org/json/json/20201115/json-20201115.jar)**
# Overview
@@ -246,6 +246,8 @@ and artifactId "json". For example:
[https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav](https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav)
~~~
20210307 Recent commits and potentially breaking fix to JSONPointer
20201115 Recent commits and first release after project structure change
20200518 Recent commits and snapshot before project structure change

View File

@@ -13,17 +13,14 @@ apply plugin: 'maven-publish'
repositories {
mavenLocal()
mavenCentral()
maven {
url = uri('https://oss.sonatype.org/content/repositories/snapshots')
}
maven {
url = uri('http://repo.maven.apache.org/maven2')
}
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13.1'
testImplementation 'com.jayway.jsonpath:json-path:2.1.0'
testImplementation 'org.mockito:mockito-core:1.9.5'
}

View File

@@ -3,7 +3,7 @@
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>v20200429-SNAPSHOT</version>
<version>20210307</version>
<packaging>bundle</packaging>
<name>JSON in Java</name>
@@ -80,7 +80,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -567,6 +567,14 @@ public class JSONArray implements Iterable<Object> {
return this.myArrayList.size();
}
/**
* Removes all of the elements from this JSONArray.
* The JSONArray will be empty after this call returns.
*/
public void clear() {
this.myArrayList.clear();
}
/**
* Get the optional object value associated with an index.
*
@@ -1374,6 +1382,8 @@ public class JSONArray implements Iterable<Object> {
if (!((JSONArray)valueThis).similar(valueOther)) {
return false;
}
} else if (valueThis instanceof Number && valueOther instanceof Number) {
return JSONObject.isNumberSimilar((Number)valueThis, (Number)valueOther);
} else if (!valueThis.equals(valueOther)) {
return false;
}

View File

@@ -151,10 +151,10 @@ public class JSONObject {
return "null";
}
}
/**
* Regular Expression Pattern that matches JSON Numbers. This is primarily used for
* output to guarantee that we are always writing valid JSON.
* output to guarantee that we are always writing valid JSON.
*/
static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
@@ -175,10 +175,10 @@ public class JSONObject {
* Construct an empty JSONObject.
*/
public JSONObject() {
// HashMap is used on purpose to ensure that elements are unordered by
// HashMap is used on purpose to ensure that elements are unordered by
// the specification.
// JSON tends to be a portable transfer format to allows the container
// implementations to rearrange their items for a faster element
// JSON tends to be a portable transfer format to allows the container
// implementations to rearrange their items for a faster element
// retrieval based on associative access.
// Therefore, an implementation mustn't rely on the order of the item.
this.map = new HashMap<String, Object>();
@@ -239,9 +239,9 @@ public class JSONObject {
if (c != ':') {
throw x.syntaxError("Expected a ':' after a key");
}
// Use syntaxError(..) to include error location
if (key != null) {
// Check if key exists
if (this.opt(key) != null) {
@@ -350,11 +350,11 @@ public class JSONObject {
* method from being serialized:
* <pre>
* &#64;JSONPropertyName("FullName")
* &#64;JSONPropertyIgnore
* &#64;JSONPropertyIgnore
* public String getName() { return this.name; }
* </pre>
* <p>
*
*
* @param bean
* An object that has getter methods that should be used to make
* a JSONObject.
@@ -448,12 +448,12 @@ public class JSONObject {
}
}
}
/**
* Constructor to specify an initial capacity of the internal map. Useful for library
* Constructor to specify an initial capacity of the internal map. Useful for library
* internal calls where we know, or at least can best guess, how big this JSONObject
* will be.
*
*
* @param initialCapacity initial capacity of the internal map.
*/
protected JSONObject(int initialCapacity){
@@ -576,7 +576,7 @@ public class JSONObject {
/**
* Get the enum value associated with a key.
*
*
* @param <E>
* Enum Type
* @param clazz
@@ -630,7 +630,7 @@ public class JSONObject {
* A key string.
* @return The numeric value.
* @throws JSONException
* if the key is not found or if the value cannot
* if the key is not found or if the value cannot
* be converted to BigInteger.
*/
public BigInteger getBigInteger(String key) throws JSONException {
@@ -929,7 +929,7 @@ public class JSONObject {
* modify the JSONObject. Use with caution.
*
* @see Set#iterator()
*
*
* @return An iterator of the keys.
*/
public Iterator<String> keys() {
@@ -950,10 +950,10 @@ public class JSONObject {
/**
* Get a set of entries of the JSONObject. These are raw values and may not
* match what is returned by the JSONObject get* and opt* functions. Modifying
* match what is returned by the JSONObject get* and opt* functions. Modifying
* the returned EntrySet or the Entry objects contained therein will modify the
* backing JSONObject. This does not return a clone or a read-only view.
*
*
* Use with caution.
*
* @see Map#entrySet()
@@ -973,6 +973,14 @@ public class JSONObject {
return this.map.size();
}
/**
* Removes all of the elements from this JSONObject.
* The JSONObject will be empty after this call returns.
*/
public void clear() {
this.map.clear();
}
/**
* Check if JSONObject is empty.
*
@@ -1039,7 +1047,7 @@ public class JSONObject {
/**
* Get the enum value associated with a key.
*
*
* @param <E>
* Enum Type
* @param clazz
@@ -1054,7 +1062,7 @@ public class JSONObject {
/**
* Get the enum value associated with a key.
*
*
* @param <E>
* Enum Type
* @param clazz
@@ -1148,7 +1156,7 @@ public class JSONObject {
* @param val value to convert
* @param defaultValue default value to return is the conversion doesn't work or is null.
* @return BigDecimal conversion of the original value, or the defaultValue if unable
* to convert.
* to convert.
*/
static BigDecimal objectToBigDecimal(Object val, BigDecimal defaultValue) {
if (NULL.equals(val)) {
@@ -1161,8 +1169,7 @@ public class JSONObject {
return new BigDecimal((BigInteger) val);
}
if (val instanceof Double || val instanceof Float){
final double d = ((Number) val).doubleValue();
if(Double.isNaN(d)) {
if (!numberIsFinite((Number)val)) {
return defaultValue;
}
return new BigDecimal(((Number) val).doubleValue());
@@ -1199,7 +1206,7 @@ public class JSONObject {
* @param val value to convert
* @param defaultValue default value to return is the conversion doesn't work or is null.
* @return BigInteger conversion of the original value, or the defaultValue if unable
* to convert.
* to convert.
*/
static BigInteger objectToBigInteger(Object val, BigInteger defaultValue) {
if (NULL.equals(val)) {
@@ -1212,11 +1219,10 @@ public class JSONObject {
return ((BigDecimal) val).toBigInteger();
}
if (val instanceof Double || val instanceof Float){
final double d = ((Number) val).doubleValue();
if(Double.isNaN(d)) {
if (!numberIsFinite((Number)val)) {
return defaultValue;
}
return new BigDecimal(d).toBigInteger();
return new BigDecimal(((Number) val).doubleValue()).toBigInteger();
}
if (val instanceof Long || val instanceof Integer
|| val instanceof Short || val instanceof Byte){
@@ -1224,7 +1230,7 @@ public class JSONObject {
}
// don't check if it's a string in case of unchecked Number subclasses
try {
// the other opt functions handle implicit conversions, i.e.
// the other opt functions handle implicit conversions, i.e.
// jo.put("double",1.1d);
// jo.optInt("double"); -- will return 1, not an error
// this conversion to BigDecimal then to BigInteger is to maintain
@@ -1398,10 +1404,10 @@ public class JSONObject {
if (val == null) {
return defaultValue;
}
return val.longValue();
}
/**
* Get an optional {@link Number} value associated with a key, or <code>null</code>
* if there is no such key or if the value is not a number. If the value is a string,
@@ -1436,14 +1442,14 @@ public class JSONObject {
if (val instanceof Number){
return (Number) val;
}
try {
return stringToNumber(val.toString());
} catch (Exception e) {
return defaultValue;
}
}
/**
* Get an optional string associated with a key. It returns an empty string
* if there is no such key. If the value is not a string and is not null,
@@ -1552,7 +1558,7 @@ public class JSONObject {
// if the first letter in the key is not uppercase, then skip.
// This is to maintain backwards compatibility before PR406
// (https://github.com/stleary/JSON-java/pull/406/)
if (Character.isLowerCase(key.charAt(0))) {
if (key.length() == 0 || Character.isLowerCase(key.charAt(0))) {
return null;
}
if (key.length() == 1) {
@@ -1729,7 +1735,7 @@ public class JSONObject {
public JSONObject put(String key, double value) throws JSONException {
return this.put(key, Double.valueOf(value));
}
/**
* Put a key/float pair in the JSONObject.
*
@@ -1873,7 +1879,7 @@ public class JSONObject {
}
/**
* Creates a JSONPointer using an initialization string and tries to
* Creates a JSONPointer using an initialization string and tries to
* match it to an item within this JSONObject. For example, given a
* JSONObject initialized with this document:
* <pre>
@@ -1881,13 +1887,13 @@ public class JSONObject {
* "a":{"b":"c"}
* }
* </pre>
* and this JSONPointer string:
* and this JSONPointer string:
* <pre>
* "/a/b"
* </pre>
* Then this method will return the String "c".
* A JSONPointerException may be thrown from code called by this method.
*
*
* @param jsonPointer string that can be used to create a JSONPointer
* @return the item matched by the JSONPointer, otherwise null
*/
@@ -1895,7 +1901,7 @@ public class JSONObject {
return query(new JSONPointer(jsonPointer));
}
/**
* Uses a user initialized JSONPointer and tries to
* Uses a user initialized JSONPointer and tries to
* match it to an item within this JSONObject. For example, given a
* JSONObject initialized with this document:
* <pre>
@@ -1903,24 +1909,24 @@ public class JSONObject {
* "a":{"b":"c"}
* }
* </pre>
* and this JSONPointer:
* and this JSONPointer:
* <pre>
* "/a/b"
* </pre>
* Then this method will return the String "c".
* A JSONPointerException may be thrown from code called by this method.
*
*
* @param jsonPointer string that can be used to create a JSONPointer
* @return the item matched by the JSONPointer, otherwise null
*/
public Object query(JSONPointer jsonPointer) {
return jsonPointer.queryFrom(this);
}
/**
* Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key.
*
*
* @param jsonPointer the string representation of the JSON pointer
* @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
@@ -1928,11 +1934,11 @@ public class JSONObject {
public Object optQuery(String jsonPointer) {
return optQuery(new JSONPointer(jsonPointer));
}
/**
* Queries and returns a value from this object using {@code jsonPointer}, or
* returns null if the query fails due to a missing key.
*
*
* @param jsonPointer The JSON pointer
* @return the queried value or {@code null}
* @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
@@ -2073,6 +2079,8 @@ public class JSONObject {
if (!((JSONArray)valueThis).similar(valueOther)) {
return false;
}
} else if (valueThis instanceof Number && valueOther instanceof Number) {
return isNumberSimilar((Number)valueThis, (Number)valueOther);
} else if (!valueThis.equals(valueOther)) {
return false;
}
@@ -2082,10 +2090,59 @@ public class JSONObject {
return false;
}
}
/**
* Compares two numbers to see if they are similar.
*
* If either of the numbers are Double or Float instances, then they are checked to have
* a finite value. If either value is not finite (NaN or &#177;infinity), then this
* function will always return false. If both numbers are finite, they are first checked
* to be the same type and implement {@link Comparable}. If they do, then the actual
* {@link Comparable#compareTo(Object)} is called. If they are not the same type, or don't
* implement Comparable, then they are converted to {@link BigDecimal}s. Finally the
* BigDecimal values are compared using {@link BigDecimal#compareTo(BigDecimal)}.
*
* @param l the Left value to compare. Can not be <code>null</code>.
* @param r the right value to compare. Can not be <code>null</code>.
* @return true if the numbers are similar, false otherwise.
*/
static boolean isNumberSimilar(Number l, Number r) {
if (!numberIsFinite(l) || !numberIsFinite(r)) {
// non-finite numbers are never similar
return false;
}
// if the classes are the same and implement Comparable
// then use the built in compare first.
if(l.getClass().equals(r.getClass()) && l instanceof Comparable) {
@SuppressWarnings({ "rawtypes", "unchecked" })
int compareTo = ((Comparable)l).compareTo(r);
return compareTo==0;
}
// BigDecimal should be able to handle all of our number types that we support through
// documentation. Convert to BigDecimal first, then use the Compare method to
// decide equality.
final BigDecimal lBigDecimal = objectToBigDecimal(l, null);
final BigDecimal rBigDecimal = objectToBigDecimal(r, null);
if (lBigDecimal == null || rBigDecimal == null) {
return false;
}
return lBigDecimal.compareTo(rBigDecimal) == 0;
}
private static boolean numberIsFinite(Number n) {
if (n instanceof Double && (((Double) n).isInfinite() || ((Double) n).isNaN())) {
return false;
} else if (n instanceof Float && (((Float) n).isInfinite() || ((Float) n).isNaN())) {
return false;
}
return true;
}
/**
* Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
*
*
* @param val value to test
* @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
*/
@@ -2093,12 +2150,12 @@ public class JSONObject {
return val.indexOf('.') > -1 || val.indexOf('e') > -1
|| val.indexOf('E') > -1 || "-0".equals(val);
}
/**
* Converts a string to a number using the narrowest possible type. Possible
* Converts a string to a number using the narrowest possible type. Possible
* returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
* When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
*
*
* @param val value to convert
* @return Number representation of the value.
* @throws NumberFormatException thrown if the value is not a valid number. A public
@@ -2147,7 +2204,7 @@ public class JSONObject {
// integer representation.
// This will narrow any values to the smallest reasonable Object representation
// (Integer, Long, or BigInteger)
// BigInteger down conversion: We use a similar bitLenth compare as
// BigInteger#intValueExact uses. Increases GC, but objects hold
// only what they need. i.e. Less runtime overhead if the value is
@@ -2216,18 +2273,8 @@ public class JSONObject {
* If o is a non-finite number.
*/
public static void testValidity(Object o) throws JSONException {
if (o != null) {
if (o instanceof Double) {
if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
throw new JSONException(
"JSON does not allow non-finite numbers.");
}
} else if (o instanceof Float) {
if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
throw new JSONException(
"JSON does not allow non-finite numbers.");
}
}
if (o instanceof Number && !numberIsFinite((Number) o)) {
throw new JSONException("JSON does not allow non-finite numbers.");
}
}
@@ -2260,7 +2307,7 @@ public class JSONObject {
* <p><b>
* Warning: This method assumes that the data structure is acyclical.
* </b>
*
*
* @return a printable, displayable, portable, transmittable representation
* of the object, beginning with <code>{</code>&nbsp;<small>(left
* brace)</small> and ending with <code>}</code>&nbsp;<small>(right
@@ -2277,11 +2324,11 @@ public class JSONObject {
/**
* Make a pretty-printed JSON text of this JSONObject.
*
*
* <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* <pre>{@code {"key": 1}}</pre>
*
*
* <p>If an object has 2 or more keys, then it will be output across
* multiple lines: <pre>{@code {
* "key1": 1,
@@ -2354,7 +2401,7 @@ public class JSONObject {
*/
public static Object wrap(Object object) {
try {
if (object == null) {
if (NULL.equals(object)) {
return NULL;
}
if (object instanceof JSONObject || object instanceof JSONArray
@@ -2459,11 +2506,11 @@ public class JSONObject {
/**
* Write the contents of the JSONObject as JSON text to a writer.
*
*
* <p>If <pre>{@code indentFactor > 0}</pre> and the {@link JSONObject}
* has only one key, then the object will be output on a single line:
* <pre>{@code {"key": 1}}</pre>
*
*
* <p>If an object has 2 or more keys, then it will be output across
* multiple lines: <pre>{@code {
* "key1": 1,
@@ -2565,7 +2612,7 @@ public class JSONObject {
}
return results;
}
/**
* Create a new JSONException in a common format for incorrect conversions.
* @param key name of the key
@@ -2581,7 +2628,7 @@ public class JSONObject {
"JSONObject[" + quote(key) + "] is not a " + valueType + "."
, cause);
}
/**
* Create a new JSONException in a common format for incorrect conversions.
* @param key name of the key

View File

@@ -187,10 +187,11 @@ public class JSONPointer {
this.refTokens = new ArrayList<String>(refTokens);
}
/**
* @see https://tools.ietf.org/html/rfc6901#section-3
*/
private static String unescape(String token) {
return token.replace("~1", "/").replace("~0", "~")
.replace("\\\"", "\"")
.replace("\\\\", "\\");
return token.replace("~1", "/").replace("~0", "~");
}
/**
@@ -263,16 +264,15 @@ public class JSONPointer {
/**
* 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. Backslashes
* and double quote chars are also 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 https://tools.ietf.org/html/rfc6901#section-3
*/
private static String escape(String token) {
return token.replace("~", "~0")
.replace("/", "~1")
.replace("\\", "\\\\")
.replace("\"", "\\\"");
.replace("/", "~1");
}
/**

View File

@@ -1254,4 +1254,19 @@ public class JSONArrayTest {
assertEquals("index " + i + " are equal", a1.get(i), a2.get(i));
}
}
/**
* Tests if calling JSONArray clear() method actually makes the JSONArray empty
*/
@Test(expected = JSONException.class)
public void jsonArrayClearMethodTest() {
//Adds random stuff to the JSONArray
JSONArray jsonArray = new JSONArray();
jsonArray.put(123);
jsonArray.put("456");
jsonArray.put(new JSONArray());
jsonArray.clear(); //Clears the JSONArray
assertTrue("expected jsonArray.length() == 0", jsonArray.length() == 0); //Check if its length is 0
jsonArray.getInt(0); //Should throws org.json.JSONException: JSONArray[0] not found
}
}

View File

@@ -115,10 +115,17 @@ public class JSONObjectTest {
.put("key2", 2)
.put("key3", new String(string1));
assertFalse("Should eval to false", obj1.similar(obj2));
JSONObject obj4 = new JSONObject()
.put("key1", "abc")
.put("key2", 2.0)
.put("key3", new String(string1));
assertFalse("Should eval to false", obj1.similar(obj2));
assertTrue("Should eval to true", obj1.similar(obj3));
assertTrue("Should eval to true", obj1.similar(obj4));
}
@Test
@@ -3208,4 +3215,19 @@ public class JSONObjectTest {
assertNotNull("'empty_json_array' should be an array", jsonObject.getJSONArray("empty_json_array"));
assertEquals("'empty_json_array' should have a length of 0", 0, jsonObject.getJSONArray("empty_json_array").length());
}
/**
* Tests if calling JSONObject clear() method actually makes the JSONObject empty
*/
@Test(expected = JSONException.class)
public void jsonObjectClearMethodTest() {
//Adds random stuff to the JSONObject
JSONObject jsonObject = new JSONObject();
jsonObject.put("key1", 123);
jsonObject.put("key2", "456");
jsonObject.put("key3", new JSONObject());
jsonObject.clear(); //Clears the JSONObject
assertTrue("expected jsonObject.length() == 0", jsonObject.length() == 0); //Check if its length is 0
jsonObject.getInt("key1"); //Should throws org.json.JSONException: JSONObject["asd"] not found
}
}

View File

@@ -117,14 +117,24 @@ public class JSONPointerTest {
assertSame(document.get("m~n"), query("/m~0n"));
}
/**
* We pass backslashes as-is
*
* @see https://tools.ietf.org/html/rfc6901#section-3
*/
@Test
public void backslashEscaping() {
assertSame(document.get("i\\j"), query("/i\\\\j"));
public void backslashHandling() {
assertSame(document.get("i\\j"), query("/i\\j"));
}
/**
* We pass quotations as-is
*
* @see https://tools.ietf.org/html/rfc6901#section-3
*/
@Test
public void quotationEscaping() {
assertSame(document.get("k\"l"), query("/k\\\\\\\"l"));
public void quotationHandling() {
assertSame(document.get("k\"l"), query("/k\"l"));
}
@Test
@@ -189,7 +199,7 @@ public class JSONPointerTest {
.append("\"")
.append(0)
.build();
assertEquals("/obj/other~0key/another~1key/\\\"/0", pointer.toString());
assertEquals("/obj/other~0key/another~1key/\"/0", pointer.toString());
}
@Test