Make default adapters stricter; improve exception messages (#2000)

* Make default adapters stricter; improve exception messages

* Reduce scope of synchronized blocks

* Improve JsonReader.getPath / getPreviousPath Javadoc
This commit is contained in:
Marcono1234 2021-11-01 23:08:04 +01:00 committed by GitHub
parent b3188c1132
commit b4dab86b10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 327 additions and 77 deletions

View File

@ -71,11 +71,11 @@ public enum ToNumberPolicy implements ToNumberStrategy {
try { try {
Double d = Double.valueOf(value); Double d = Double.valueOf(value);
if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) { if ((d.isInfinite() || d.isNaN()) && !in.isLenient()) {
throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPath()); throw new MalformedJsonException("JSON forbids NaN and infinities: " + d + "; at path " + in.getPreviousPath());
} }
return d; return d;
} catch (NumberFormatException doubleE) { } catch (NumberFormatException doubleE) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPath(), doubleE); throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), doubleE);
} }
} }
} }
@ -91,7 +91,7 @@ public enum ToNumberPolicy implements ToNumberStrategy {
try { try {
return new BigDecimal(value); return new BigDecimal(value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPath(), e); throw new JsonParseException("Cannot parse " + value + "; at path " + in.getPreviousPath(), e);
} }
} }
} }

View File

@ -72,30 +72,36 @@ public final class DateTypeAdapter extends TypeAdapter<Date> {
in.nextNull(); in.nextNull();
return null; return null;
} }
return deserializeToDate(in.nextString()); return deserializeToDate(in);
} }
private synchronized Date deserializeToDate(String json) { private Date deserializeToDate(JsonReader in) throws IOException {
String s = in.nextString();
synchronized (dateFormats) {
for (DateFormat dateFormat : dateFormats) { for (DateFormat dateFormat : dateFormats) {
try { try {
return dateFormat.parse(json); return dateFormat.parse(s);
} catch (ParseException ignored) {} } catch (ParseException ignored) {}
} }
}
try { try {
return ISO8601Utils.parse(json, new ParsePosition(0)); return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException(json, e); throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
} }
} }
@Override public synchronized void write(JsonWriter out, Date value) throws IOException { @Override public void write(JsonWriter out, Date value) throws IOException {
if (value == null) { if (value == null) {
out.nullValue(); out.nullValue();
return; return;
} }
String dateFormatAsString = dateFormats.get(0).format(value);
DateFormat dateFormat = dateFormats.get(0);
String dateFormatAsString;
synchronized (dateFormats) {
dateFormatAsString = dateFormat.format(value);
}
out.value(dateFormatAsString); out.value(dateFormatAsString);
} }
} }

View File

@ -130,10 +130,13 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
out.nullValue(); out.nullValue();
return; return;
} }
DateFormat dateFormat = dateFormats.get(0);
String dateFormatAsString;
synchronized (dateFormats) { synchronized (dateFormats) {
String dateFormatAsString = dateFormats.get(0).format(value); dateFormatAsString = dateFormat.format(value);
out.value(dateFormatAsString);
} }
out.value(dateFormatAsString);
} }
@Override @Override
@ -142,11 +145,12 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
in.nextNull(); in.nextNull();
return null; return null;
} }
Date date = deserializeToDate(in.nextString()); Date date = deserializeToDate(in);
return dateType.deserialize(date); return dateType.deserialize(date);
} }
private Date deserializeToDate(String s) { private Date deserializeToDate(JsonReader in) throws IOException {
String s = in.nextString();
synchronized (dateFormats) { synchronized (dateFormats) {
for (DateFormat dateFormat : dateFormats) { for (DateFormat dateFormat : dateFormats) {
try { try {
@ -158,7 +162,7 @@ public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T>
try { try {
return ISO8601Utils.parse(s, new ParsePosition(0)); return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException(s, e); throw new JsonSyntaxException("Failed parsing '" + s + "' as Date; at path " + in.getPreviousPath(), e);
} }
} }

View File

@ -304,12 +304,19 @@ public final class JsonTreeReader extends JsonReader {
stack[stackSize++] = newTop; stack[stackSize++] = newTop;
} }
@Override public String getPath() { private String getPath(boolean usePreviousPath) {
StringBuilder result = new StringBuilder().append('$'); StringBuilder result = new StringBuilder().append('$');
for (int i = 0; i < stackSize; i++) { for (int i = 0; i < stackSize; i++) {
if (stack[i] instanceof JsonArray) { if (stack[i] instanceof JsonArray) {
if (++i < stackSize && stack[i] instanceof Iterator) { if (++i < stackSize && stack[i] instanceof Iterator) {
result.append('[').append(pathIndices[i]).append(']'); int pathIndex = pathIndices[i];
// If index is last path element it points to next array element; have to decrement
// `- 1` covers case where iterator for next element is on stack
// `- 2` covers case where peek() already pushed next element onto stack
if (usePreviousPath && pathIndex > 0 && (i == stackSize - 1 || i == stackSize - 2)) {
pathIndex--;
}
result.append('[').append(pathIndex).append(']');
} }
} else if (stack[i] instanceof JsonObject) { } else if (stack[i] instanceof JsonObject) {
if (++i < stackSize && stack[i] instanceof Iterator) { if (++i < stackSize && stack[i] instanceof Iterator) {
@ -323,6 +330,14 @@ public final class JsonTreeReader extends JsonReader {
return result.toString(); return result.toString();
} }
@Override public String getPreviousPath() {
return getPath(true);
}
@Override public String getPath() {
return getPath(false);
}
private String locationString() { private String locationString() {
return " at path " + getPath(); return " at path " + getPath();
} }

View File

@ -72,7 +72,7 @@ public final class NumberTypeAdapter extends TypeAdapter<Number> {
case STRING: case STRING:
return toNumberStrategy.readNumber(in); return toNumberStrategy.readNumber(in);
default: default:
throw new JsonSyntaxException("Expecting number, got: " + jsonToken); throw new JsonSyntaxException("Expecting number, got: " + jsonToken + "; at path " + in.getPath());
} }
} }

View File

@ -92,22 +92,21 @@ public final class TypeAdapters {
boolean set; boolean set;
switch (tokenType) { switch (tokenType) {
case NUMBER: case NUMBER:
set = in.nextInt() != 0; case STRING:
int intValue = in.nextInt();
if (intValue == 0) {
set = false;
} else if (intValue == 1) {
set = true;
} else {
throw new JsonSyntaxException("Invalid bitset value " + intValue + ", expected 0 or 1; at path " + in.getPreviousPath());
}
break; break;
case BOOLEAN: case BOOLEAN:
set = in.nextBoolean(); set = in.nextBoolean();
break; break;
case STRING:
String stringValue = in.nextString();
try {
set = Integer.parseInt(stringValue) != 0;
} catch (NumberFormatException e) {
throw new JsonSyntaxException(
"Error: Expecting: bitset number value (1, 0), Found: " + stringValue);
}
break;
default: default:
throw new JsonSyntaxException("Invalid bitset value type: " + tokenType); throw new JsonSyntaxException("Invalid bitset value type: " + tokenType + "; at path " + in.getPath());
} }
if (set) { if (set) {
bitset.set(i); bitset.set(i);
@ -178,12 +177,18 @@ public final class TypeAdapters {
in.nextNull(); in.nextNull();
return null; return null;
} }
int intValue;
try { try {
int intValue = in.nextInt(); intValue = in.nextInt();
return (byte) intValue;
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException(e);
} }
// Allow up to 255 to support unsigned values
if (intValue > 255 || intValue < Byte.MIN_VALUE) {
throw new JsonSyntaxException("Lossy conversion from " + intValue + " to byte; at path " + in.getPreviousPath());
}
return (byte) intValue;
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
@ -201,11 +206,18 @@ public final class TypeAdapters {
in.nextNull(); in.nextNull();
return null; return null;
} }
int intValue;
try { try {
return (short) in.nextInt(); intValue = in.nextInt();
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException(e);
} }
// Allow up to 65535 to support unsigned values
if (intValue > 65535 || intValue < Short.MIN_VALUE) {
throw new JsonSyntaxException("Lossy conversion from " + intValue + " to short; at path " + in.getPreviousPath());
}
return (short) intValue;
} }
@Override @Override
public void write(JsonWriter out, Number value) throws IOException { public void write(JsonWriter out, Number value) throws IOException {
@ -352,7 +364,7 @@ public final class TypeAdapters {
} }
String str = in.nextString(); String str = in.nextString();
if (str.length() != 1) { if (str.length() != 1) {
throw new JsonSyntaxException("Expecting character, got: " + str); throw new JsonSyntaxException("Expecting character, got: " + str + "; at " + in.getPreviousPath());
} }
return str.charAt(0); return str.charAt(0);
} }
@ -391,10 +403,11 @@ public final class TypeAdapters {
in.nextNull(); in.nextNull();
return null; return null;
} }
String s = in.nextString();
try { try {
return new BigDecimal(in.nextString()); return new BigDecimal(s);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException("Failed parsing '" + s + "' as BigDecimal; at path " + in.getPreviousPath(), e);
} }
} }
@ -409,10 +422,11 @@ public final class TypeAdapters {
in.nextNull(); in.nextNull();
return null; return null;
} }
String s = in.nextString();
try { try {
return new BigInteger(in.nextString()); return new BigInteger(s);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException("Failed parsing '" + s + "' as BigInteger; at path " + in.getPreviousPath(), e);
} }
} }
@ -525,7 +539,12 @@ public final class TypeAdapters {
in.nextNull(); in.nextNull();
return null; return null;
} }
return java.util.UUID.fromString(in.nextString()); String s = in.nextString();
try {
return java.util.UUID.fromString(s);
} catch (IllegalArgumentException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as UUID; at path " + in.getPreviousPath(), e);
}
} }
@Override @Override
public void write(JsonWriter out, UUID value) throws IOException { public void write(JsonWriter out, UUID value) throws IOException {
@ -538,7 +557,12 @@ public final class TypeAdapters {
public static final TypeAdapter<Currency> CURRENCY = new TypeAdapter<Currency>() { public static final TypeAdapter<Currency> CURRENCY = new TypeAdapter<Currency>() {
@Override @Override
public Currency read(JsonReader in) throws IOException { public Currency read(JsonReader in) throws IOException {
return Currency.getInstance(in.nextString()); String s = in.nextString();
try {
return Currency.getInstance(s);
} catch (IllegalArgumentException e) {
throw new JsonSyntaxException("Failed parsing '" + s + "' as Currency; at path " + in.getPreviousPath(), e);
}
} }
@Override @Override
public void write(JsonWriter out, Currency value) throws IOException { public void write(JsonWriter out, Currency value) throws IOException {
@ -866,7 +890,7 @@ public final class TypeAdapters {
T1 result = typeAdapter.read(in); T1 result = typeAdapter.read(in);
if (result != null && !requestedType.isInstance(result)) { if (result != null && !requestedType.isInstance(result)) {
throw new JsonSyntaxException("Expected a " + requestedType.getName() throw new JsonSyntaxException("Expected a " + requestedType.getName()
+ " but was " + result.getClass().getName()); + " but was " + result.getClass().getName() + "; at path " + in.getPreviousPath());
} }
return result; return result;
} }

View File

@ -28,6 +28,7 @@ import java.io.IOException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date;
/** /**
* Adapter for java.sql.Date. Although this class appears stateless, it is not. * Adapter for java.sql.Date. Although this class appears stateless, it is not.
@ -50,21 +51,33 @@ final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
} }
@Override @Override
public synchronized java.sql.Date read(JsonReader in) throws IOException { public java.sql.Date read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
} }
String s = in.nextString();
try { try {
final long utilDate = format.parse(in.nextString()).getTime(); Date utilDate;
return new java.sql.Date(utilDate); synchronized (this) {
utilDate = format.parse(s);
}
return new java.sql.Date(utilDate.getTime());
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Date; at path " + in.getPreviousPath(), e);
} }
} }
@Override @Override
public synchronized void write(JsonWriter out, java.sql.Date value) throws IOException { public void write(JsonWriter out, java.sql.Date value) throws IOException {
out.value(value == null ? null : format.format(value)); if (value == null) {
out.nullValue();
return;
}
String dateString;
synchronized (this) {
dateString = format.format(value);
}
out.value(dateString);
} }
} }

View File

@ -50,20 +50,31 @@ final class SqlTimeTypeAdapter extends TypeAdapter<Time> {
private SqlTimeTypeAdapter() { private SqlTimeTypeAdapter() {
} }
@Override public synchronized Time read(JsonReader in) throws IOException { @Override public Time read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) { if (in.peek() == JsonToken.NULL) {
in.nextNull(); in.nextNull();
return null; return null;
} }
String s = in.nextString();
try { try {
Date date = format.parse(in.nextString()); synchronized (this) {
Date date = format.parse(s);
return new Time(date.getTime()); return new Time(date.getTime());
}
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException("Failed parsing '" + s + "' as SQL Time; at path " + in.getPreviousPath(), e);
} }
} }
@Override public synchronized void write(JsonWriter out, Time value) throws IOException { @Override public void write(JsonWriter out, Time value) throws IOException {
out.value(value == null ? null : format.format(value)); if (value == null) {
out.nullValue();
return;
}
String timeString;
synchronized (this) {
timeString = format.format(value);
}
out.value(timeString);
} }
} }

View File

@ -1454,19 +1454,19 @@ public class JsonReader implements Closeable {
return " at line " + line + " column " + column + " path " + getPath(); return " at line " + line + " column " + column + " path " + getPath();
} }
/** private String getPath(boolean usePreviousPath) {
* Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
* the current location in the JSON value.
*/
public String getPath() {
StringBuilder result = new StringBuilder().append('$'); StringBuilder result = new StringBuilder().append('$');
for (int i = 0, size = stackSize; i < size; i++) { for (int i = 0; i < stackSize; i++) {
switch (stack[i]) { switch (stack[i]) {
case JsonScope.EMPTY_ARRAY: case JsonScope.EMPTY_ARRAY:
case JsonScope.NONEMPTY_ARRAY: case JsonScope.NONEMPTY_ARRAY:
result.append('[').append(pathIndices[i]).append(']'); int pathIndex = pathIndices[i];
// If index is last path element it points to next array element; have to decrement
if (usePreviousPath && pathIndex > 0 && i == stackSize - 1) {
pathIndex--;
}
result.append('[').append(pathIndex).append(']');
break; break;
case JsonScope.EMPTY_OBJECT: case JsonScope.EMPTY_OBJECT:
case JsonScope.DANGLING_NAME: case JsonScope.DANGLING_NAME:
case JsonScope.NONEMPTY_OBJECT: case JsonScope.NONEMPTY_OBJECT:
@ -1475,7 +1475,6 @@ public class JsonReader implements Closeable {
result.append(pathNames[i]); result.append(pathNames[i]);
} }
break; break;
case JsonScope.NONEMPTY_DOCUMENT: case JsonScope.NONEMPTY_DOCUMENT:
case JsonScope.EMPTY_DOCUMENT: case JsonScope.EMPTY_DOCUMENT:
case JsonScope.CLOSED: case JsonScope.CLOSED:
@ -1485,6 +1484,41 @@ public class JsonReader implements Closeable {
return result.toString(); return result.toString();
} }
/**
* Returns a <a href="https://goessner.net/articles/JsonPath/">JsonPath</a>
* in <i>dot-notation</i> to the previous (or current) location in the JSON document:
* <ul>
* <li>For JSON arrays the path points to the index of the previous element.<br>
* If no element has been consumed yet it uses the index 0 (even if there are no elements).</li>
* <li>For JSON objects the path points to the last property, or to the current
* property if its value has not been consumed yet.</li>
* </ul>
*
* <p>This method can be useful to add additional context to exception messages
* <i>after</i> a value has been consumed.
*/
public String getPreviousPath() {
return getPath(true);
}
/**
* Returns a <a href="https://goessner.net/articles/JsonPath/">JsonPath</a>
* in <i>dot-notation</i> to the next (or current) location in the JSON document:
* <ul>
* <li>For JSON arrays the path points to the index of the next element (even
* if there are no further elements).</li>
* <li>For JSON objects the path points to the last property, or to the current
* property if its value has not been consumed yet.</li>
* </ul>
*
* <p>This method can be useful to add additional context to exception messages
* <i>before</i> a value is consumed, for example when the {@linkplain #peek() peeked}
* token is unexpected.
*/
public String getPath() {
return getPath(false);
}
/** /**
* Unescapes the character identified by the character or characters that * Unescapes the character identified by the character or characters that
* immediately follow a backslash. The backslash '\' should have already * immediately follow a backslash. The backslash '\' should have already

View File

@ -332,6 +332,20 @@ public class DefaultTypeAdaptersTest extends TestCase {
json = "[true,false,true,true,true,true,false,false,true,false,false]"; json = "[true,false,true,true,true,true,false,false,true,false,false]";
assertEquals(expected, gson.fromJson(json, BitSet.class)); assertEquals(expected, gson.fromJson(json, BitSet.class));
try {
gson.fromJson("[1, []]", BitSet.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("Invalid bitset value type: BEGIN_ARRAY; at path $[1]", e.getMessage());
}
try {
gson.fromJson("[1, 2]", BitSet.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("Invalid bitset value 2, expected 0 or 1; at path $[1]", e.getMessage());
}
} }
public void testDefaultDateSerialization() { public void testDefaultDateSerialization() {
@ -567,7 +581,7 @@ public class DefaultTypeAdaptersTest extends TestCase {
gson.fromJson("\"abc\"", JsonObject.class); gson.fromJson("\"abc\"", JsonObject.class);
fail(); fail();
} catch (JsonSyntaxException expected) { } catch (JsonSyntaxException expected) {
assertEquals("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive", assertEquals("Expected a com.google.gson.JsonObject but was com.google.gson.JsonPrimitive; at path $",
expected.getMessage()); expected.getMessage());
} }
} }

View File

@ -16,6 +16,8 @@
package com.google.gson.functional; package com.google.gson.functional;
import static org.junit.Assert.assertArrayEquals;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
@ -63,16 +65,75 @@ public class PrimitiveTest extends TestCase {
assertEquals("1", gson.toJson(1, Byte.class)); assertEquals("1", gson.toJson(1, Byte.class));
} }
public void testByteDeserialization() {
Byte boxed = gson.fromJson("1", Byte.class);
assertEquals(1, (byte)boxed);
byte primitive = gson.fromJson("1", byte.class);
assertEquals(1, primitive);
byte[] bytes = gson.fromJson("[-128, 0, 127, 255]", byte[].class);
assertArrayEquals(new byte[] {-128, 0, 127, -1}, bytes);
}
public void testByteDeserializationLossy() {
try {
gson.fromJson("-129", byte.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("Lossy conversion from -129 to byte; at path $", e.getMessage());
}
try {
gson.fromJson("256", byte.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("Lossy conversion from 256 to byte; at path $", e.getMessage());
}
try {
gson.fromJson("2147483648", byte.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 path $", e.getMessage());
}
}
public void testShortSerialization() { public void testShortSerialization() {
assertEquals("1", gson.toJson(1, short.class)); assertEquals("1", gson.toJson(1, short.class));
assertEquals("1", gson.toJson(1, Short.class)); assertEquals("1", gson.toJson(1, Short.class));
} }
public void testByteDeserialization() { public void testShortDeserialization() {
Byte target = gson.fromJson("1", Byte.class); Short boxed = gson.fromJson("1", Short.class);
assertEquals(1, (byte)target); assertEquals(1, (short)boxed);
byte primitive = gson.fromJson("1", byte.class); short primitive = gson.fromJson("1", short.class);
assertEquals(1, primitive); assertEquals(1, primitive);
short[] shorts = gson.fromJson("[-32768, 0, 32767, 65535]", short[].class);
assertArrayEquals(new short[] {-32768, 0, 32767, -1}, shorts);
}
public void testShortDeserializationLossy() {
try {
gson.fromJson("-32769", short.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("Lossy conversion from -32769 to short; at path $", e.getMessage());
}
try {
gson.fromJson("65536", short.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("Lossy conversion from 65536 to short; at path $", e.getMessage());
}
try {
gson.fromJson("2147483648", short.class);
fail();
} catch (JsonSyntaxException e) {
assertEquals("java.lang.NumberFormatException: Expected an int but was 2147483648 at line 1 column 11 path $", e.getMessage());
}
} }
public void testPrimitiveIntegerAutoboxedInASingleElementArraySerialization() { public void testPrimitiveIntegerAutoboxedInASingleElementArraySerialization() {

View File

@ -46,110 +46,154 @@ public class JsonReaderPathTest {
@Test public void path() throws IOException { @Test public void path() throws IOException {
JsonReader reader = factory.create("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}"); JsonReader reader = factory.create("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}");
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$.", reader.getPreviousPath());
assertEquals("$.", reader.getPath()); assertEquals("$.", reader.getPath());
reader.nextName(); reader.nextName();
assertEquals("$.a", reader.getPreviousPath());
assertEquals("$.a", reader.getPath()); assertEquals("$.a", reader.getPath());
reader.beginArray(); reader.beginArray();
assertEquals("$.a[0]", reader.getPreviousPath());
assertEquals("$.a[0]", reader.getPath()); assertEquals("$.a[0]", reader.getPath());
reader.nextInt(); reader.nextInt();
assertEquals("$.a[0]", reader.getPreviousPath());
assertEquals("$.a[1]", reader.getPath()); assertEquals("$.a[1]", reader.getPath());
reader.nextBoolean(); reader.nextBoolean();
assertEquals("$.a[1]", reader.getPreviousPath());
assertEquals("$.a[2]", reader.getPath()); assertEquals("$.a[2]", reader.getPath());
reader.nextBoolean(); reader.nextBoolean();
assertEquals("$.a[2]", reader.getPreviousPath());
assertEquals("$.a[3]", reader.getPath()); assertEquals("$.a[3]", reader.getPath());
reader.nextNull(); reader.nextNull();
assertEquals("$.a[3]", reader.getPreviousPath());
assertEquals("$.a[4]", reader.getPath()); assertEquals("$.a[4]", reader.getPath());
reader.nextString(); reader.nextString();
assertEquals("$.a[4]", reader.getPreviousPath());
assertEquals("$.a[5]", reader.getPath()); assertEquals("$.a[5]", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$.a[5].", reader.getPreviousPath());
assertEquals("$.a[5].", reader.getPath()); assertEquals("$.a[5].", reader.getPath());
reader.nextName(); reader.nextName();
assertEquals("$.a[5].c", reader.getPreviousPath());
assertEquals("$.a[5].c", reader.getPath()); assertEquals("$.a[5].c", reader.getPath());
reader.nextString(); reader.nextString();
assertEquals("$.a[5].c", reader.getPreviousPath());
assertEquals("$.a[5].c", reader.getPath()); assertEquals("$.a[5].c", reader.getPath());
reader.endObject(); reader.endObject();
assertEquals("$.a[5]", reader.getPreviousPath());
assertEquals("$.a[6]", reader.getPath()); assertEquals("$.a[6]", reader.getPath());
reader.beginArray(); reader.beginArray();
assertEquals("$.a[6][0]", reader.getPreviousPath());
assertEquals("$.a[6][0]", reader.getPath()); assertEquals("$.a[6][0]", reader.getPath());
reader.nextInt(); reader.nextInt();
assertEquals("$.a[6][0]", reader.getPreviousPath());
assertEquals("$.a[6][1]", reader.getPath()); assertEquals("$.a[6][1]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$.a[6]", reader.getPreviousPath());
assertEquals("$.a[7]", reader.getPath()); assertEquals("$.a[7]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$.a", reader.getPreviousPath());
assertEquals("$.a", reader.getPath()); assertEquals("$.a", reader.getPath());
reader.endObject(); reader.endObject();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
@Test public void objectPath() throws IOException { @Test public void objectPath() throws IOException {
JsonReader reader = factory.create("{\"a\":1,\"b\":2}"); JsonReader reader = factory.create("{\"a\":1,\"b\":2}");
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$.", reader.getPreviousPath());
assertEquals("$.", reader.getPath()); assertEquals("$.", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$.", reader.getPreviousPath());
assertEquals("$.", reader.getPath()); assertEquals("$.", reader.getPath());
reader.nextName(); reader.nextName();
assertEquals("$.a", reader.getPreviousPath());
assertEquals("$.a", reader.getPath()); assertEquals("$.a", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$.a", reader.getPreviousPath());
assertEquals("$.a", reader.getPath()); assertEquals("$.a", reader.getPath());
reader.nextInt(); reader.nextInt();
assertEquals("$.a", reader.getPreviousPath());
assertEquals("$.a", reader.getPath()); assertEquals("$.a", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$.a", reader.getPreviousPath());
assertEquals("$.a", reader.getPath()); assertEquals("$.a", reader.getPath());
reader.nextName(); reader.nextName();
assertEquals("$.b", reader.getPreviousPath());
assertEquals("$.b", reader.getPath()); assertEquals("$.b", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$.b", reader.getPreviousPath());
assertEquals("$.b", reader.getPath()); assertEquals("$.b", reader.getPath());
reader.nextInt(); reader.nextInt();
assertEquals("$.b", reader.getPreviousPath());
assertEquals("$.b", reader.getPath()); assertEquals("$.b", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$.b", reader.getPreviousPath());
assertEquals("$.b", reader.getPath()); assertEquals("$.b", reader.getPath());
reader.endObject(); reader.endObject();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.close(); reader.close();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
@Test public void arrayPath() throws IOException { @Test public void arrayPath() throws IOException {
JsonReader reader = factory.create("[1,2]"); JsonReader reader = factory.create("[1,2]");
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.beginArray(); reader.beginArray();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[0]", reader.getPath()); assertEquals("$[0]", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[0]", reader.getPath()); assertEquals("$[0]", reader.getPath());
reader.nextInt(); reader.nextInt();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[1]", reader.getPath()); assertEquals("$[1]", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[1]", reader.getPath()); assertEquals("$[1]", reader.getPath());
reader.nextInt(); reader.nextInt();
assertEquals("$[1]", reader.getPreviousPath());
assertEquals("$[2]", reader.getPath()); assertEquals("$[2]", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$[1]", reader.getPreviousPath());
assertEquals("$[2]", reader.getPath()); assertEquals("$[2]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.peek(); reader.peek();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.close(); reader.close();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
@ -160,9 +204,11 @@ public class JsonReaderPathTest {
reader.setLenient(true); reader.setLenient(true);
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
@ -171,6 +217,7 @@ public class JsonReaderPathTest {
reader.beginArray(); reader.beginArray();
reader.skipValue(); reader.skipValue();
reader.skipValue(); reader.skipValue();
assertEquals("$[1]", reader.getPreviousPath());
assertEquals("$[2]", reader.getPath()); assertEquals("$[2]", reader.getPath());
} }
@ -178,17 +225,21 @@ public class JsonReaderPathTest {
JsonReader reader = factory.create("{\"a\":1}"); JsonReader reader = factory.create("{\"a\":1}");
reader.beginObject(); reader.beginObject();
reader.skipValue(); reader.skipValue();
assertEquals("$.null", reader.getPreviousPath());
assertEquals("$.null", reader.getPath()); assertEquals("$.null", reader.getPath());
} }
@Test public void skipObjectValues() throws IOException { @Test public void skipObjectValues() throws IOException {
JsonReader reader = factory.create("{\"a\":1,\"b\":2}"); JsonReader reader = factory.create("{\"a\":1,\"b\":2}");
reader.beginObject(); reader.beginObject();
assertEquals("$.", reader.getPreviousPath());
assertEquals("$.", reader.getPath()); assertEquals("$.", reader.getPath());
reader.nextName(); reader.nextName();
reader.skipValue(); reader.skipValue();
assertEquals("$.null", reader.getPreviousPath());
assertEquals("$.null", reader.getPath()); assertEquals("$.null", reader.getPath());
reader.nextName(); reader.nextName();
assertEquals("$.b", reader.getPreviousPath());
assertEquals("$.b", reader.getPath()); assertEquals("$.b", reader.getPath());
} }
@ -196,46 +247,63 @@ public class JsonReaderPathTest {
JsonReader reader = factory.create("[[1,2,3],4]"); JsonReader reader = factory.create("[[1,2,3],4]");
reader.beginArray(); reader.beginArray();
reader.skipValue(); reader.skipValue();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[1]", reader.getPath()); assertEquals("$[1]", reader.getPath());
} }
@Test public void arrayOfObjects() throws IOException { @Test public void arrayOfObjects() throws IOException {
JsonReader reader = factory.create("[{},{},{}]"); JsonReader reader = factory.create("[{},{},{}]");
reader.beginArray(); reader.beginArray();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[0]", reader.getPath()); assertEquals("$[0]", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$[0].", reader.getPreviousPath());
assertEquals("$[0].", reader.getPath()); assertEquals("$[0].", reader.getPath());
reader.endObject(); reader.endObject();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[1]", reader.getPath()); assertEquals("$[1]", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$[1].", reader.getPreviousPath());
assertEquals("$[1].", reader.getPath()); assertEquals("$[1].", reader.getPath());
reader.endObject(); reader.endObject();
assertEquals("$[1]", reader.getPreviousPath());
assertEquals("$[2]", reader.getPath()); assertEquals("$[2]", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$[2].", reader.getPreviousPath());
assertEquals("$[2].", reader.getPath()); assertEquals("$[2].", reader.getPath());
reader.endObject(); reader.endObject();
assertEquals("$[2]", reader.getPreviousPath());
assertEquals("$[3]", reader.getPath()); assertEquals("$[3]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
@Test public void arrayOfArrays() throws IOException { @Test public void arrayOfArrays() throws IOException {
JsonReader reader = factory.create("[[],[],[]]"); JsonReader reader = factory.create("[[],[],[]]");
reader.beginArray(); reader.beginArray();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[0]", reader.getPath()); assertEquals("$[0]", reader.getPath());
reader.beginArray(); reader.beginArray();
assertEquals("$[0][0]", reader.getPreviousPath());
assertEquals("$[0][0]", reader.getPath()); assertEquals("$[0][0]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$[0]", reader.getPreviousPath());
assertEquals("$[1]", reader.getPath()); assertEquals("$[1]", reader.getPath());
reader.beginArray(); reader.beginArray();
assertEquals("$[1][0]", reader.getPreviousPath());
assertEquals("$[1][0]", reader.getPath()); assertEquals("$[1][0]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$[1]", reader.getPreviousPath());
assertEquals("$[2]", reader.getPath()); assertEquals("$[2]", reader.getPath());
reader.beginArray(); reader.beginArray();
assertEquals("$[2][0]", reader.getPreviousPath());
assertEquals("$[2][0]", reader.getPath()); assertEquals("$[2][0]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$[2]", reader.getPreviousPath());
assertEquals("$[3]", reader.getPath()); assertEquals("$[3]", reader.getPath());
reader.endArray(); reader.endArray();
assertEquals("$", reader.getPreviousPath());
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }