From aa9125bb8183e0821754d1462f410349ec995f95 Mon Sep 17 00:00:00 2001 From: jwilson Date: Sat, 21 Jan 2017 23:06:16 -0500 Subject: [PATCH] Fix some bugs and increase tests for ObjectJsonReader. https://github.com/square/moshi/issues/89 --- .../com/squareup/moshi/ObjectJsonReader.java | 144 +++- .../moshi/BufferedSourceJsonReaderTest.java | 692 +-------------- .../com/squareup/moshi/JsonReaderFactory.java | 46 + .../squareup/moshi/JsonReaderPathTest.java | 29 +- .../com/squareup/moshi/JsonReaderTest.java | 800 ++++++++++++++++++ .../squareup/moshi/ObjectJsonReaderTest.java | 163 +++- .../java/com/squareup/moshi/TestUtil.java | 7 + 7 files changed, 1125 insertions(+), 756 deletions(-) create mode 100644 moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java create mode 100644 moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java diff --git a/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java b/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java index 3f61ede..41fe078 100644 --- a/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java +++ b/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java @@ -16,6 +16,7 @@ package com.squareup.moshi; import java.io.IOException; +import java.math.BigDecimal; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -86,16 +87,14 @@ final class ObjectJsonReader extends JsonReader { // If the iterator isn't empty push its first value onto the stack. if (iterator.hasNext()) { - stack[stackSize] = iterator.next(); - stackSize++; + push(iterator.next()); } } @Override public void endArray() throws IOException { ListIterator peeked = require(ListIterator.class, Token.END_ARRAY); if (peeked.hasNext()) { - throw new JsonDataException( - "Expected " + Token.END_ARRAY + " but was " + peek() + " at path " + getPath()); + throw typeMismatch(peeked, Token.END_ARRAY); } remove(); } @@ -109,16 +108,14 @@ final class ObjectJsonReader extends JsonReader { // If the iterator isn't empty push its first value onto the stack. if (iterator.hasNext()) { - stack[stackSize] = iterator.next(); - stackSize++; + push(iterator.next()); } } @Override public void endObject() throws IOException { Iterator peeked = require(Iterator.class, Token.END_OBJECT); if (peeked instanceof ListIterator || peeked.hasNext()) { - throw new JsonDataException( - "Expected " + Token.END_OBJECT + " but was " + peek() + " at path " + getPath()); + throw typeMismatch(peeked, Token.END_OBJECT); } pathNames[stackSize - 1] = null; remove(); @@ -148,22 +145,31 @@ final class ObjectJsonReader extends JsonReader { if (peeked == null) return Token.NULL; if (peeked == JSON_READER_CLOSED) throw new IllegalStateException("JsonReader is closed"); - throw new JsonDataException("Expected a JSON value but was a " + peeked.getClass().getName() - + " at path " + getPath()); + throw typeMismatch(peeked, "a JSON value"); } @Override public String nextName() throws IOException { - Object peeked = require(Map.Entry.class, Token.NAME); + Map.Entry peeked = require(Map.Entry.class, Token.NAME); // Swap the Map.Entry for its value on the stack and return its key. - String result = (String) ((Map.Entry) peeked).getKey(); - stack[stackSize - 1] = ((Map.Entry) peeked).getValue(); + String result = stringKey(peeked); + stack[stackSize - 1] = peeked.getValue(); pathNames[stackSize - 2] = result; return result; } @Override int selectName(Options options) throws IOException { - throw new UnsupportedOperationException(); + Map.Entry peeked = require(Map.Entry.class, Token.NAME); + String name = stringKey(peeked); + for (int i = 0, length = options.strings.length; i < length; i++) { + // Swap the Map.Entry for its value on the stack and return its key. + if (options.strings[i].equals(name)) { + stack[stackSize - 1] = peeked.getValue(); + pathNames[stackSize - 2] = name; + return i; + } + } + return -1; } @Override public String nextString() throws IOException { @@ -173,7 +179,14 @@ final class ObjectJsonReader extends JsonReader { } @Override int selectString(Options options) throws IOException { - throw new UnsupportedOperationException(); + String peeked = require(String.class, Token.STRING); + for (int i = 0, length = options.strings.length; i < length; i++) { + if (options.strings[i].equals(peeked)) { + remove(); + return i; + } + } + return -1; } @Override public boolean nextBoolean() throws IOException { @@ -189,21 +202,74 @@ final class ObjectJsonReader extends JsonReader { } @Override public double nextDouble() throws IOException { - Number peeked = require(Number.class, Token.NUMBER); + Object peeked = require(Object.class, Token.NUMBER); + + double result; + if (peeked instanceof Number) { + result = ((Number) peeked).doubleValue(); + } else if (peeked instanceof String) { + try { + result = Double.parseDouble((String) peeked); + } catch (NumberFormatException e) { + throw typeMismatch(peeked, Token.NUMBER); + } + } else { + throw typeMismatch(peeked, Token.NUMBER); + } + if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) { + throw new JsonEncodingException("JSON forbids NaN and infinities: " + result + + " at path " + getPath()); + } remove(); - return peeked.doubleValue(); // TODO(jwilson): precision check? + return result; } @Override public long nextLong() throws IOException { - Number peeked = require(Number.class, Token.NUMBER); + Object peeked = require(Object.class, Token.NUMBER); + + long result; + if (peeked instanceof Number) { + result = ((Number) peeked).longValue(); + } else if (peeked instanceof String) { + try { + result = Long.parseLong((String) peeked); + } catch (NumberFormatException e) { + try { + BigDecimal asDecimal = new BigDecimal((String) peeked); + result = asDecimal.longValueExact(); + } catch (NumberFormatException e2) { + throw typeMismatch(peeked, Token.NUMBER); + } + } + } else { + throw typeMismatch(peeked, Token.NUMBER); + } remove(); - return peeked.longValue(); // TODO(jwilson): precision check? + return result; } @Override public int nextInt() throws IOException { - Number peeked = require(Number.class, Token.NUMBER); + Object peeked = require(Object.class, Token.NUMBER); + + int result; + if (peeked instanceof Number) { + result = ((Number) peeked).intValue(); + } else if (peeked instanceof String) { + try { + result = Integer.parseInt((String) peeked); + } catch (NumberFormatException e) { + try { + BigDecimal asDecimal = new BigDecimal((String) peeked); + result = asDecimal.intValueExact(); + } catch (NumberFormatException e2) { + throw typeMismatch(peeked, Token.NUMBER); + } + } + } else { + throw typeMismatch(peeked, Token.NUMBER); + } remove(); - return peeked.intValue(); // TODO(jwilson): precision check? + return result; } @Override public void skipValue() throws IOException { @@ -233,11 +299,10 @@ final class ObjectJsonReader extends JsonReader { } @Override void promoteNameToValue() throws IOException { - Object peeked = require(Map.Entry.class, Token.NAME); + Map.Entry peeked = require(Map.Entry.class, Token.NAME); - stackSize++; - stack[stackSize - 2] = ((Map.Entry) peeked).getValue(); - stack[stackSize - 1] = ((Map.Entry) peeked).getKey(); + push(peeked.getKey()); + stack[stackSize - 2] = peeked.getValue(); } @Override public void close() throws IOException { @@ -247,6 +312,13 @@ final class ObjectJsonReader extends JsonReader { stackSize = 1; } + private void push(Object newTop) { + if (stackSize == stack.length) { + throw new JsonDataException("Nesting too deep at " + getPath()); + } + stack[stackSize++] = newTop; + } + /** * Returns the top of the stack which is required to be a {@code type}. Throws if this reader is * closed, or if the type isn't what was expected. @@ -263,8 +335,23 @@ final class ObjectJsonReader extends JsonReader { if (peeked == JSON_READER_CLOSED) { throw new IllegalStateException("JsonReader is closed"); } - throw new JsonDataException( - "Expected " + expected + " but was " + peek() + " at path " + getPath()); + throw typeMismatch(peeked, expected); + } + + private String stringKey(Map.Entry entry) { + Object name = entry.getKey(); + if (name instanceof String) return (String) name; + throw typeMismatch(name, Token.NAME); + } + + private JsonDataException typeMismatch(Object value, Object expected) { + if (value == null) { + throw new JsonDataException( + "Expected " + expected + " but was null at path " + getPath()); + } else { + throw new JsonDataException("Expected " + expected + " but was " + value + ", a " + + value.getClass().getName() + ", at path " + getPath()); + } } /** @@ -282,8 +369,7 @@ final class ObjectJsonReader extends JsonReader { Object parent = stack[stackSize - 1]; if (parent instanceof Iterator && ((Iterator) parent).hasNext()) { - stack[stackSize] = ((Iterator) parent).next(); - stackSize++; + push(((Iterator) parent).next()); } } } diff --git a/moshi/src/test/java/com/squareup/moshi/BufferedSourceJsonReaderTest.java b/moshi/src/test/java/com/squareup/moshi/BufferedSourceJsonReaderTest.java index 7a0c2b1..a1dd935 100644 --- a/moshi/src/test/java/com/squareup/moshi/BufferedSourceJsonReaderTest.java +++ b/moshi/src/test/java/com/squareup/moshi/BufferedSourceJsonReaderTest.java @@ -19,6 +19,8 @@ import java.io.EOFException; import java.io.IOException; import java.util.Arrays; import okio.Buffer; +import okio.ForwardingSource; +import okio.Okio; import org.junit.Ignore; import org.junit.Test; @@ -32,6 +34,7 @@ import static com.squareup.moshi.JsonReader.Token.NULL; import static com.squareup.moshi.JsonReader.Token.NUMBER; import static com.squareup.moshi.JsonReader.Token.STRING; import static com.squareup.moshi.TestUtil.newReader; +import static com.squareup.moshi.TestUtil.repeat; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -51,34 +54,6 @@ public final class BufferedSourceJsonReaderTest { assertThat(buffer.size()).isEqualTo(0); } - @Test public void readArray() throws IOException { - JsonReader reader = newReader("[true, true]"); - reader.beginArray(); - assertThat(reader.nextBoolean()).isTrue(); - assertThat(reader.nextBoolean()).isTrue(); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void readEmptyArray() throws IOException { - JsonReader reader = newReader("[]"); - reader.beginArray(); - assertThat(reader.hasNext()).isFalse(); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void readObject() throws IOException { - JsonReader reader = newReader("{\"a\": \"android\", \"b\": \"banana\"}"); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - assertThat(reader.nextString()).isEqualTo("android"); - assertThat(reader.nextName()).isEqualTo("b"); - assertThat(reader.nextString()).isEqualTo("banana"); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void readObjectBuffer() throws IOException { Buffer buffer = new Buffer().writeUtf8("{\"a\": \"android\", \"b\": \"banana\"}"); JsonReader reader = JsonReader.of(buffer); @@ -93,7 +68,7 @@ public final class BufferedSourceJsonReaderTest { @Test public void readObjectSource() throws IOException { Buffer buffer = new Buffer().writeUtf8("{\"a\": \"android\", \"b\": \"banana\"}"); - JsonReader reader = JsonReader.of(buffer); + JsonReader reader = JsonReader.of(Okio.buffer(new ForwardingSource(buffer) {})); reader.beginObject(); assertThat(reader.nextName()).isEqualTo("a"); assertThat(reader.nextString()).isEqualTo("android"); @@ -103,147 +78,6 @@ public final class BufferedSourceJsonReaderTest { assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); } - @Test public void readEmptyObject() throws IOException { - JsonReader reader = newReader("{}"); - reader.beginObject(); - assertThat(reader.hasNext()).isFalse(); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipArray() throws IOException { - JsonReader reader = newReader("{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("b"); - assertThat(reader.nextInt()).isEqualTo(123); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipArrayAfterPeek() throws Exception { - JsonReader reader = newReader("{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - assertThat(reader.peek()).isEqualTo(BEGIN_ARRAY); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("b"); - assertThat(reader.nextInt()).isEqualTo(123); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipTopLevelObject() throws Exception { - JsonReader reader = newReader("{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"); - reader.skipValue(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipObject() throws IOException { - JsonReader reader = newReader( - "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("b"); - reader.skipValue(); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipObjectAfterPeek() throws Exception { - String json = "{" + " \"one\": { \"num\": 1 }" - + ", \"two\": { \"num\": 2 }" + ", \"three\": { \"num\": 3 }" + "}"; - JsonReader reader = newReader(json); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("one"); - assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("two"); - assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("three"); - reader.skipValue(); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipInteger() throws IOException { - JsonReader reader = newReader("{\"a\":123456789,\"b\":-123456789}"); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("b"); - reader.skipValue(); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipDouble() throws IOException { - JsonReader reader = newReader("{\"a\":-123.456e-789,\"b\":123456789.0}"); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - reader.skipValue(); - assertThat(reader.nextName()).isEqualTo("b"); - reader.skipValue(); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void failOnUnknownFailsOnUnknownObjectValue() throws IOException { - JsonReader reader = newReader("{\"a\": 123}"); - reader.setFailOnUnknown(true); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - try { - reader.skipValue(); - fail(); - } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Cannot skip unexpected NUMBER at $.a"); - } - // Confirm that the reader is left in a consistent state after the exception. - reader.setFailOnUnknown(false); - assertThat(reader.nextInt()).isEqualTo(123); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void failOnUnknownFailsOnUnknownArrayElement() throws IOException { - JsonReader reader = newReader("[\"a\", 123]"); - reader.setFailOnUnknown(true); - reader.beginArray(); - assertThat(reader.nextString()).isEqualTo("a"); - try { - reader.skipValue(); - fail(); - } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Cannot skip unexpected NUMBER at $[1]"); - } - // Confirm that the reader is left in a consistent state after the exception. - reader.setFailOnUnknown(false); - assertThat(reader.nextInt()).isEqualTo(123); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void helloWorld() throws IOException { - String json = "{\n" + - " \"hello\": true,\n" + - " \"foo\": [\"world\"]\n" + - "}"; - JsonReader reader = newReader(json); - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("hello"); - assertThat(reader.nextBoolean()).isTrue(); - assertThat(reader.nextName()).isEqualTo("foo"); - reader.beginArray(); - assertThat(reader.nextString()).isEqualTo("world"); - reader.endArray(); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void nullSource() { try { JsonReader.of(null); @@ -252,65 +86,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void emptyString() throws Exception { - try { - newReader("").beginArray(); - fail(); - } catch (EOFException expected) { - } - try { - newReader("").beginObject(); - fail(); - } catch (EOFException expected) { - } - } - - @Test public void characterUnescaping() throws IOException { - String json = "[\"a\"," - + "\"a\\\"\"," - + "\"\\\"\"," - + "\":\"," - + "\",\"," - + "\"\\b\"," - + "\"\\f\"," - + "\"\\n\"," - + "\"\\r\"," - + "\"\\t\"," - + "\" \"," - + "\"\\\\\"," - + "\"{\"," - + "\"}\"," - + "\"[\"," - + "\"]\"," - + "\"\\u0000\"," - + "\"\\u0019\"," - + "\"\\u20AC\"" - + "]"; - JsonReader reader = newReader(json); - reader.beginArray(); - assertThat(reader.nextString()).isEqualTo("a"); - assertThat(reader.nextString()).isEqualTo("a\""); - assertThat(reader.nextString()).isEqualTo("\""); - assertThat(reader.nextString()).isEqualTo(":"); - assertThat(reader.nextString()).isEqualTo(","); - assertThat(reader.nextString()).isEqualTo("\b"); - assertThat(reader.nextString()).isEqualTo("\f"); - assertThat(reader.nextString()).isEqualTo("\n"); - assertThat(reader.nextString()).isEqualTo("\r"); - assertThat(reader.nextString()).isEqualTo("\t"); - assertThat(reader.nextString()).isEqualTo(" "); - assertThat(reader.nextString()).isEqualTo("\\"); - assertThat(reader.nextString()).isEqualTo("{"); - assertThat(reader.nextString()).isEqualTo("}"); - assertThat(reader.nextString()).isEqualTo("["); - assertThat(reader.nextString()).isEqualTo("]"); - assertThat(reader.nextString()).isEqualTo("\0"); - assertThat(reader.nextString()).isEqualTo("\u0019"); - assertThat(reader.nextString()).isEqualTo("\u20AC"); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void unescapingInvalidCharacters() throws IOException { String json = "[\"\\u000g\"]"; JsonReader reader = newReader(json); @@ -344,84 +119,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void integersWithFractionalPartSpecified() throws IOException { - JsonReader reader = newReader("[1.0,1.0,1.0]"); - reader.beginArray(); - assertThat(reader.nextDouble()).isEqualTo(1.0); - assertThat(reader.nextInt()).isEqualTo(1); - assertThat(reader.nextLong()).isEqualTo(1L); - } - - @Test public void doubles() throws IOException { - String json = "[-0.0," - + "1.0," - + "1.7976931348623157E308," - + "4.9E-324," - + "0.0," - + "-0.5," - + "2.2250738585072014E-308," - + "3.141592653589793," - + "2.718281828459045]"; - JsonReader reader = newReader(json); - reader.beginArray(); - assertThat(reader.nextDouble()).isEqualTo(-0.0); - assertThat(reader.nextDouble()).isEqualTo(1.0); - assertThat(reader.nextDouble()).isEqualTo(1.7976931348623157E308); - assertThat(reader.nextDouble()).isEqualTo(4.9E-324); - assertThat(reader.nextDouble()).isEqualTo(0.0); - assertThat(reader.nextDouble()).isEqualTo(-0.5); - assertThat(reader.nextDouble()).isEqualTo(2.2250738585072014E-308); - assertThat(reader.nextDouble()).isEqualTo(3.141592653589793); - assertThat(reader.nextDouble()).isEqualTo(2.718281828459045); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void strictNonFiniteDoubles() throws IOException { - String json = "[NaN]"; - JsonReader reader = newReader(json); - reader.beginArray(); - try { - reader.nextDouble(); - fail(); - } catch (JsonEncodingException expected) { - } - } - - @Test public void strictQuotedNonFiniteDoubles() throws IOException { - String json = "[\"NaN\"]"; - JsonReader reader = newReader(json); - reader.beginArray(); - try { - reader.nextDouble(); - fail(); - } catch (JsonEncodingException expected) { - assertThat(expected).hasMessageContaining("NaN"); - } - } - - @Test public void lenientNonFiniteDoubles() throws IOException { - String json = "[NaN, -Infinity, Infinity]"; - JsonReader reader = newReader(json); - reader.setLenient(true); - reader.beginArray(); - assertThat(Double.isNaN(reader.nextDouble())).isTrue(); - assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); - assertThat(reader.nextDouble()).isEqualTo(Double.POSITIVE_INFINITY); - reader.endArray(); - } - - @Test public void lenientQuotedNonFiniteDoubles() throws IOException { - String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]"; - JsonReader reader = newReader(json); - reader.setLenient(true); - reader.beginArray(); - assertThat(reader.nextDouble()).isNaN(); - assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); - assertThat(reader.nextDouble()).isEqualTo(Double.POSITIVE_INFINITY); - reader.endArray(); - } - @Test public void strictNonFiniteDoublesWithSkipValue() throws IOException { String json = "[NaN]"; JsonReader reader = newReader(json); @@ -433,39 +130,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void longs() throws IOException { - String json = "[0,0,0," - + "1,1,1," - + "-1,-1,-1," - + "-9223372036854775808," - + "9223372036854775807]"; - JsonReader reader = newReader(json); - reader.beginArray(); - assertThat(reader.nextLong()).isEqualTo(0L); - assertThat(reader.nextInt()).isEqualTo(0); - assertThat(reader.nextDouble()).isEqualTo(0.0d); - assertThat(reader.nextLong()).isEqualTo(1L); - assertThat(reader.nextInt()).isEqualTo(1); - assertThat(reader.nextDouble()).isEqualTo(1.0d); - assertThat(reader.nextLong()).isEqualTo(-1L); - assertThat(reader.nextInt()).isEqualTo(-1); - assertThat(reader.nextDouble()).isEqualTo(-1.0d); - try { - reader.nextInt(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextLong()).isEqualTo(Long.MIN_VALUE); - try { - reader.nextInt(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextLong()).isEqualTo(Long.MAX_VALUE); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test @Ignore public void numberWithOctalPrefix() throws IOException { String json = "[01]"; JsonReader reader = newReader(json); @@ -495,15 +159,6 @@ public final class BufferedSourceJsonReaderTest { assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); } - @Test public void booleans() throws IOException { - JsonReader reader = newReader("[true,false]"); - reader.beginArray(); - assertThat(reader.nextBoolean()).isTrue(); - assertThat(reader.nextBoolean()).isFalse(); - reader.endArray(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void peekingUnquotedStringsPrefixedWithBooleans() throws IOException { JsonReader reader = newReader("[truey]"); reader.setLenient(true); @@ -749,122 +404,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void nextFailuresDoNotAdvance() throws IOException { - JsonReader reader = newReader("{\"a\":true}"); - reader.beginObject(); - try { - reader.nextString(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextName()).isEqualTo("a"); - try { - reader.nextName(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.beginArray(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.endArray(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.beginObject(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.endObject(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextBoolean()).isTrue(); - try { - reader.nextString(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.nextName(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.beginArray(); - fail(); - } catch (JsonDataException expected) { - } - try { - reader.endArray(); - fail(); - } catch (JsonDataException expected) { - } - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - reader.close(); - } - - @Test public void integerMismatchWithDoubleDoesNotAdvance() throws IOException { - JsonReader reader = newReader("[1.5]"); - reader.beginArray(); - try { - reader.nextInt(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextDouble()).isEqualTo(1.5d); - reader.endArray(); - } - - @Test public void integerMismatchWithLongDoesNotAdvance() throws IOException { - JsonReader reader = newReader("[9223372036854775807]"); - reader.beginArray(); - try { - reader.nextInt(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextLong()).isEqualTo(9223372036854775807L); - reader.endArray(); - } - - @Test public void longMismatchWithDoubleDoesNotAdvance() throws IOException { - JsonReader reader = newReader("[1.5]"); - reader.beginArray(); - try { - reader.nextLong(); - fail(); - } catch (JsonDataException expected) { - } - assertThat(reader.nextDouble()).isEqualTo(1.5d); - reader.endArray(); - } - - @Test public void stringNullIsNotNull() throws IOException { - JsonReader reader = newReader("[\"null\"]"); - reader.beginArray(); - try { - reader.nextNull(); - fail(); - } catch (JsonDataException expected) { - } - } - - @Test public void nullLiteralIsNotAString() throws IOException { - JsonReader reader = newReader("[null]"); - reader.beginArray(); - try { - reader.nextString(); - fail(); - } catch (JsonDataException expected) { - } - } - @Test public void strictNameValueSeparator() throws IOException { JsonReader reader = newReader("{\"a\"=true}"); reader.beginObject(); @@ -1334,38 +873,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void topLevelValueTypes() throws IOException { - JsonReader reader1 = newReader("true"); - assertThat(reader1.nextBoolean()).isTrue(); - assertThat(reader1.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - - JsonReader reader2 = newReader("false"); - assertThat(reader2.nextBoolean()).isFalse(); - assertThat(reader2.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - - JsonReader reader3 = newReader("null"); - assertThat(reader3.nextNull()).isNull(); - assertThat(reader3.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - - JsonReader reader4 = newReader("123"); - assertThat(reader4.nextInt()).isEqualTo(123); - assertThat(reader4.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - - JsonReader reader5 = newReader("123.4"); - assertThat(reader5.nextDouble()).isEqualTo(123.4); - assertThat(reader5.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - - JsonReader reader6 = newReader("\"a\""); - assertThat(reader6.nextString()).isEqualTo("a"); - assertThat(reader6.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void topLevelValueTypeWithSkipValue() throws IOException { - JsonReader reader = newReader("true"); - reader.skipValue(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test @Ignore public void bomIgnoredAsFirstCharacterOfDocument() throws IOException { JsonReader reader = newReader("\ufeff[]"); reader.beginArray(); @@ -1497,19 +1004,6 @@ public final class BufferedSourceJsonReaderTest { reader.endArray(); } - @Test public void deeplyNestedArrays() throws IOException { - JsonReader reader = newReader("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); - for (int i = 0; i < 31; i++) { - reader.beginArray(); - } - assertThat(reader.getPath()).isEqualTo("$[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]" - + "[0][0][0][0][0][0][0][0][0][0][0][0][0]"); - for (int i = 0; i < 31; i++) { - reader.endArray(); - } - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void tooDeeplyNestedArrays() throws IOException { JsonReader reader = newReader( "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); @@ -1525,28 +1019,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void deeplyNestedObjects() throws IOException { - // Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 31 levels deep. - String array = "{\"a\":%s}"; - String json = "true"; - for (int i = 0; i < 31; i++) { - json = String.format(array, json); - } - - JsonReader reader = newReader(json); - for (int i = 0; i < 31; i++) { - reader.beginObject(); - assertThat(reader.nextName()).isEqualTo("a"); - } - assertThat(reader.getPath()) - .isEqualTo("$.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a"); - assertThat(reader.nextBoolean()).isTrue(); - for (int i = 0; i < 31; i++) { - reader.endObject(); - } - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void tooDeeplyNestedObjects() throws IOException { // Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 31 levels deep. String array = "{\"a\":%s}"; @@ -1652,70 +1124,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void skipVeryLongUnquotedString() throws IOException { - JsonReader reader = newReader("[" + repeat('x', 8192) + "]"); - reader.setLenient(true); - reader.beginArray(); - reader.skipValue(); - reader.endArray(); - } - - @Test public void skipTopLevelUnquotedString() throws IOException { - JsonReader reader = newReader(repeat('x', 8192)); - reader.setLenient(true); - reader.skipValue(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void skipVeryLongQuotedString() throws IOException { - JsonReader reader = newReader("[\"" + repeat('x', 8192) + "\"]"); - reader.beginArray(); - reader.skipValue(); - reader.endArray(); - } - - @Test public void skipTopLevelQuotedString() throws IOException { - JsonReader reader = newReader("\"" + repeat('x', 8192) + "\""); - reader.setLenient(true); - reader.skipValue(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - - @Test public void stringAsNumberWithTruncatedExponent() throws IOException { - JsonReader reader = newReader("[123e]"); - reader.setLenient(true); - reader.beginArray(); - assertThat(reader.peek()).isEqualTo(STRING); - } - - @Test public void stringAsNumberWithDigitAndNonDigitExponent() throws IOException { - JsonReader reader = newReader("[123e4b]"); - reader.setLenient(true); - reader.beginArray(); - assertThat(reader.peek()).isEqualTo(STRING); - } - - @Test public void stringAsNumberWithNonDigitExponent() throws IOException { - JsonReader reader = newReader("[123eb]"); - reader.setLenient(true); - reader.beginArray(); - assertThat(reader.peek()).isEqualTo(STRING); - } - - @Test public void emptyStringName() throws IOException { - JsonReader reader = newReader("{\"\":true}"); - reader.setLenient(true); - assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); - reader.beginObject(); - assertThat(reader.peek()).isEqualTo(NAME); - assertThat(reader.nextName()).isEqualTo(""); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.BOOLEAN); - assertThat(reader.nextBoolean()).isTrue(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT); - reader.endObject(); - assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); - } - @Test public void strictExtraCommasInMaps() throws IOException { JsonReader reader = newReader("{\"a\":\"b\",}"); reader.beginObject(); @@ -1741,12 +1149,6 @@ public final class BufferedSourceJsonReaderTest { } } - private String repeat(char c, int count) { - char[] array = new char[count]; - Arrays.fill(array, c); - return new String(array); - } - @Test public void malformedDocuments() throws IOException { assertDocument("{]", BEGIN_OBJECT, JsonEncodingException.class); assertDocument("{,", BEGIN_OBJECT, JsonEncodingException.class); @@ -1804,12 +1206,6 @@ public final class BufferedSourceJsonReaderTest { } } - @Test public void validEscapes() throws IOException { - JsonReader reader = newReader("[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]"); - reader.beginArray(); - assertThat(reader.nextString()).isEqualTo("\"\\/\b\f\n\r\t"); - } - @Test public void invalidEscape() throws IOException { JsonReader reader = newReader("[\"str\\ing\"]"); reader.beginArray(); @@ -1828,68 +1224,6 @@ public final class BufferedSourceJsonReaderTest { assertThat(reader.nextString()).isEqualTo("string"); } - @Test public void selectName() throws IOException { - JsonReader.Options abc = JsonReader.Options.of("a", "b", "c"); - - JsonReader reader = newReader("{\"a\": 5, \"b\": 5, \"c\": 5, \"d\": 5}"); - reader.beginObject(); - assertEquals("$.", reader.getPath()); - - assertEquals(0, reader.selectName(abc)); - assertEquals("$.a", reader.getPath()); - assertEquals(5, reader.nextInt()); - assertEquals("$.a", reader.getPath()); - - assertEquals(1, reader.selectName(abc)); - assertEquals("$.b", reader.getPath()); - assertEquals(5, reader.nextInt()); - assertEquals("$.b", reader.getPath()); - - assertEquals(2, reader.selectName(abc)); - assertEquals("$.c", reader.getPath()); - assertEquals(5, reader.nextInt()); - assertEquals("$.c", reader.getPath()); - - // A missed selectName() doesn't advance anything, not even the path. - assertEquals(-1, reader.selectName(abc)); - assertEquals("$.c", reader.getPath()); - assertEquals(JsonReader.Token.NAME, reader.peek()); - - assertEquals("d", reader.nextName()); - assertEquals("$.d", reader.getPath()); - assertEquals(5, reader.nextInt()); - assertEquals("$.d", reader.getPath()); - - reader.endObject(); - } - - @Test public void selectString() throws IOException { - JsonReader.Options abc = JsonReader.Options.of("a", "b", "c"); - - JsonReader reader = newReader("[\"a\", \"b\", \"c\", \"d\"]"); - reader.beginArray(); - assertEquals("$[0]", reader.getPath()); - - assertEquals(0, reader.selectString(abc)); - assertEquals("$[1]", reader.getPath()); - - assertEquals(1, reader.selectString(abc)); - assertEquals("$[2]", reader.getPath()); - - assertEquals(2, reader.selectString(abc)); - assertEquals("$[3]", reader.getPath()); - - // A missed selectName() doesn't advance anything, not even the path. - assertEquals(-1, reader.selectString(abc)); - assertEquals("$[3]", reader.getPath()); - assertEquals(JsonReader.Token.STRING, reader.peek()); - - assertEquals("d", reader.nextString()); - assertEquals("$[4]", reader.getPath()); - - reader.endArray(); - } - /** Select doesn't match unquoted strings. */ @Test public void selectStringUnquoted() throws IOException { JsonReader.Options abc = JsonReader.Options.of("a", "b", "c"); @@ -1925,24 +1259,6 @@ public final class BufferedSourceJsonReaderTest { reader.endArray(); } - /** Select does match necessarily escaping. The decoded value is used in the path. */ - @Test public void selectNecessaryEscaping() throws IOException { - JsonReader.Options options = JsonReader.Options.of("\n", "\u0000", "\""); - - JsonReader reader = newReader("{\"\\n\": 5,\"\\u0000\": 5, \"\\\"\": 5}"); - reader.beginObject(); - assertEquals(0, reader.selectName(options)); - assertEquals(5, reader.nextInt()); - assertEquals("$.\n", reader.getPath()); - assertEquals(1, reader.selectName(options)); - assertEquals(5, reader.nextInt()); - assertEquals("$.\u0000", reader.getPath()); - assertEquals(2, reader.selectName(options)); - assertEquals(5, reader.nextInt()); - assertEquals("$.\"", reader.getPath()); - reader.endObject(); - } - private void assertDocument(String document, Object... expectations) throws IOException { JsonReader reader = newReader(document); reader.setLenient(true); diff --git a/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java b/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java new file mode 100644 index 0000000..21db1c5 --- /dev/null +++ b/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.moshi; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import okio.Buffer; + +abstract class JsonReaderFactory { + public static final JsonReaderFactory BUFFERED_SOURCE = new JsonReaderFactory() { + @Override public JsonReader newReader(String json) { + Buffer buffer = new Buffer().writeUtf8(json); + return JsonReader.of(buffer); + } + }; + + public static final JsonReaderFactory JSON_OBJECT = new JsonReaderFactory() { + @Override public JsonReader newReader(String json) throws IOException { + Moshi moshi = new Moshi.Builder().build(); + Object object = moshi.adapter(Object.class).lenient().fromJson(json); + return new ObjectJsonReader(object); + } + }; + + static List factories() { + return Arrays.asList( + new Object[] { BUFFERED_SOURCE }, + new Object[] { JSON_OBJECT }); + } + + abstract JsonReader newReader(String json) throws IOException; +} diff --git a/moshi/src/test/java/com/squareup/moshi/JsonReaderPathTest.java b/moshi/src/test/java/com/squareup/moshi/JsonReaderPathTest.java index 465460e..6a7741d 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonReaderPathTest.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonReaderPathTest.java @@ -16,9 +16,7 @@ package com.squareup.moshi; import java.io.IOException; -import java.util.Arrays; import java.util.List; -import okio.Buffer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -30,34 +28,13 @@ import static org.junit.Assume.assumeTrue; @RunWith(Parameterized.class) public final class JsonReaderPathTest { - interface Factory { - Factory BUFFERED_SOURCE = new Factory() { - @Override public JsonReader newReader(String json) { - Buffer buffer = new Buffer().writeUtf8(json); - return JsonReader.of(buffer); - } - }; - - Factory JSON_OBJECT = new Factory() { - @Override public JsonReader newReader(String json) throws IOException { - Moshi moshi = new Moshi.Builder().build(); - Object object = moshi.adapter(Object.class).fromJson(json); - return new ObjectJsonReader(object); - } - }; - - JsonReader newReader(String json) throws IOException; - } + @Parameter public JsonReaderFactory factory; @Parameters(name = "{0}") public static List parameters() { - return Arrays.asList( - new Object[] { Factory.BUFFERED_SOURCE}, - new Object[] { Factory.JSON_OBJECT}); + return JsonReaderFactory.factories(); } - @Parameter public Factory factory; - @Test public void path() throws IOException { JsonReader reader = factory.newReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}"); assertThat(reader.getPath()).isEqualTo("$"); @@ -208,7 +185,7 @@ public final class JsonReaderPathTest { } @Test public void multipleTopLevelValuesInOneDocument() throws IOException { - assumeTrue(factory != Factory.JSON_OBJECT); + assumeTrue(factory != JsonReaderFactory.JSON_OBJECT); JsonReader reader = factory.newReader("[][]"); reader.setLenient(true); diff --git a/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java b/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java new file mode 100644 index 0000000..db56a7e --- /dev/null +++ b/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java @@ -0,0 +1,800 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.moshi; + +import java.io.EOFException; +import java.io.IOException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static com.squareup.moshi.JsonReader.Token.BEGIN_ARRAY; +import static com.squareup.moshi.JsonReader.Token.BEGIN_OBJECT; +import static com.squareup.moshi.JsonReader.Token.NAME; +import static com.squareup.moshi.JsonReader.Token.STRING; +import static com.squareup.moshi.TestUtil.repeat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +@RunWith(Parameterized.class) +public final class JsonReaderTest { + @Parameter public JsonReaderFactory factory; + + @Parameters(name = "{0}") + public static List parameters() { + return JsonReaderFactory.factories(); + } + + JsonReader newReader(String json) throws IOException { + return factory.newReader(json); + } + + @Test public void readArray() throws IOException { + JsonReader reader = newReader("[true, true]"); + reader.beginArray(); + assertThat(reader.nextBoolean()).isTrue(); + assertThat(reader.nextBoolean()).isTrue(); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void readEmptyArray() throws IOException { + JsonReader reader = newReader("[]"); + reader.beginArray(); + assertThat(reader.hasNext()).isFalse(); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void readObject() throws IOException { + JsonReader reader = newReader("{\"a\": \"android\", \"b\": \"banana\"}"); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + assertThat(reader.nextString()).isEqualTo("android"); + assertThat(reader.nextName()).isEqualTo("b"); + assertThat(reader.nextString()).isEqualTo("banana"); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void readEmptyObject() throws IOException { + JsonReader reader = newReader("{}"); + reader.beginObject(); + assertThat(reader.hasNext()).isFalse(); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipArray() throws IOException { + JsonReader reader = newReader("{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("b"); + assertThat(reader.nextInt()).isEqualTo(123); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipArrayAfterPeek() throws Exception { + JsonReader reader = newReader("{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + assertThat(reader.peek()).isEqualTo(BEGIN_ARRAY); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("b"); + assertThat(reader.nextInt()).isEqualTo(123); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipTopLevelObject() throws Exception { + JsonReader reader = newReader("{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"); + reader.skipValue(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipObject() throws IOException { + JsonReader reader = newReader( + "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("b"); + reader.skipValue(); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipObjectAfterPeek() throws Exception { + String json = "{" + " \"one\": { \"num\": 1 }" + + ", \"two\": { \"num\": 2 }" + ", \"three\": { \"num\": 3 }" + "}"; + JsonReader reader = newReader(json); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("one"); + assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("two"); + assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("three"); + reader.skipValue(); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipInteger() throws IOException { + JsonReader reader = newReader("{\"a\":123456789,\"b\":-123456789}"); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("b"); + reader.skipValue(); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipDouble() throws IOException { + JsonReader reader = newReader("{\"a\":-123.456e-789,\"b\":123456789.0}"); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + reader.skipValue(); + assertThat(reader.nextName()).isEqualTo("b"); + reader.skipValue(); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void failOnUnknownFailsOnUnknownObjectValue() throws IOException { + JsonReader reader = newReader("{\"a\": 123}"); + reader.setFailOnUnknown(true); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + try { + reader.skipValue(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage("Cannot skip unexpected NUMBER at $.a"); + } + // Confirm that the reader is left in a consistent state after the exception. + reader.setFailOnUnknown(false); + assertThat(reader.nextInt()).isEqualTo(123); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void failOnUnknownFailsOnUnknownArrayElement() throws IOException { + JsonReader reader = newReader("[\"a\", 123]"); + reader.setFailOnUnknown(true); + reader.beginArray(); + assertThat(reader.nextString()).isEqualTo("a"); + try { + reader.skipValue(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage("Cannot skip unexpected NUMBER at $[1]"); + } + // Confirm that the reader is left in a consistent state after the exception. + reader.setFailOnUnknown(false); + assertThat(reader.nextInt()).isEqualTo(123); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void helloWorld() throws IOException { + String json = "{\n" + + " \"hello\": true,\n" + + " \"foo\": [\"world\"]\n" + + "}"; + JsonReader reader = newReader(json); + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("hello"); + assertThat(reader.nextBoolean()).isTrue(); + assertThat(reader.nextName()).isEqualTo("foo"); + reader.beginArray(); + assertThat(reader.nextString()).isEqualTo("world"); + reader.endArray(); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void emptyString() throws Exception { + try { + newReader("").beginArray(); + fail(); + } catch (EOFException expected) { + } + try { + newReader("").beginObject(); + fail(); + } catch (EOFException expected) { + } + } + + @Test public void characterUnescaping() throws IOException { + String json = "[\"a\"," + + "\"a\\\"\"," + + "\"\\\"\"," + + "\":\"," + + "\",\"," + + "\"\\b\"," + + "\"\\f\"," + + "\"\\n\"," + + "\"\\r\"," + + "\"\\t\"," + + "\" \"," + + "\"\\\\\"," + + "\"{\"," + + "\"}\"," + + "\"[\"," + + "\"]\"," + + "\"\\u0000\"," + + "\"\\u0019\"," + + "\"\\u20AC\"" + + "]"; + JsonReader reader = newReader(json); + reader.beginArray(); + assertThat(reader.nextString()).isEqualTo("a"); + assertThat(reader.nextString()).isEqualTo("a\""); + assertThat(reader.nextString()).isEqualTo("\""); + assertThat(reader.nextString()).isEqualTo(":"); + assertThat(reader.nextString()).isEqualTo(","); + assertThat(reader.nextString()).isEqualTo("\b"); + assertThat(reader.nextString()).isEqualTo("\f"); + assertThat(reader.nextString()).isEqualTo("\n"); + assertThat(reader.nextString()).isEqualTo("\r"); + assertThat(reader.nextString()).isEqualTo("\t"); + assertThat(reader.nextString()).isEqualTo(" "); + assertThat(reader.nextString()).isEqualTo("\\"); + assertThat(reader.nextString()).isEqualTo("{"); + assertThat(reader.nextString()).isEqualTo("}"); + assertThat(reader.nextString()).isEqualTo("["); + assertThat(reader.nextString()).isEqualTo("]"); + assertThat(reader.nextString()).isEqualTo("\0"); + assertThat(reader.nextString()).isEqualTo("\u0019"); + assertThat(reader.nextString()).isEqualTo("\u20AC"); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void integersWithFractionalPartSpecified() throws IOException { + JsonReader reader = newReader("[1.0,1.0,1.0]"); + reader.beginArray(); + assertThat(reader.nextDouble()).isEqualTo(1.0); + assertThat(reader.nextInt()).isEqualTo(1); + assertThat(reader.nextLong()).isEqualTo(1L); + } + + @Test public void doubles() throws IOException { + String json = "[-0.0," + + "1.0," + + "1.7976931348623157E308," + + "4.9E-324," + + "0.0," + + "-0.5," + + "2.2250738585072014E-308," + + "3.141592653589793," + + "2.718281828459045]"; + JsonReader reader = newReader(json); + reader.beginArray(); + assertThat(reader.nextDouble()).isEqualTo(-0.0); + assertThat(reader.nextDouble()).isEqualTo(1.0); + assertThat(reader.nextDouble()).isEqualTo(1.7976931348623157E308); + assertThat(reader.nextDouble()).isEqualTo(4.9E-324); + assertThat(reader.nextDouble()).isEqualTo(0.0); + assertThat(reader.nextDouble()).isEqualTo(-0.5); + assertThat(reader.nextDouble()).isEqualTo(2.2250738585072014E-308); + assertThat(reader.nextDouble()).isEqualTo(3.141592653589793); + assertThat(reader.nextDouble()).isEqualTo(2.718281828459045); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void strictNonFiniteDoubles() throws IOException { + String json = "[NaN]"; + JsonReader reader = newReader(json); + reader.beginArray(); + try { + reader.nextDouble(); + fail(); + } catch (JsonEncodingException expected) { + } + } + + @Test public void strictQuotedNonFiniteDoubles() throws IOException { + String json = "[\"NaN\"]"; + JsonReader reader = newReader(json); + reader.beginArray(); + try { + reader.nextDouble(); + fail(); + } catch (JsonEncodingException expected) { + assertThat(expected).hasMessageContaining("NaN"); + } + } + + @Test public void lenientNonFiniteDoubles() throws IOException { + String json = "[NaN, -Infinity, Infinity]"; + JsonReader reader = newReader(json); + reader.setLenient(true); + reader.beginArray(); + assertThat(Double.isNaN(reader.nextDouble())).isTrue(); + assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); + assertThat(reader.nextDouble()).isEqualTo(Double.POSITIVE_INFINITY); + reader.endArray(); + } + + @Test public void lenientQuotedNonFiniteDoubles() throws IOException { + String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]"; + JsonReader reader = newReader(json); + reader.setLenient(true); + reader.beginArray(); + assertThat(reader.nextDouble()).isNaN(); + assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); + assertThat(reader.nextDouble()).isEqualTo(Double.POSITIVE_INFINITY); + reader.endArray(); + } + + @Test public void longs() throws IOException { + assumeTrue(factory != JsonReaderFactory.JSON_OBJECT); // TODO(jwilson): fix precision checks. + String json = "[0,0,0," + + "1,1,1," + + "-1,-1,-1," + + "-9223372036854775808," + + "9223372036854775807]"; + JsonReader reader = newReader(json); + reader.beginArray(); + assertThat(reader.nextLong()).isEqualTo(0L); + assertThat(reader.nextInt()).isEqualTo(0); + assertThat(reader.nextDouble()).isEqualTo(0.0d); + assertThat(reader.nextLong()).isEqualTo(1L); + assertThat(reader.nextInt()).isEqualTo(1); + assertThat(reader.nextDouble()).isEqualTo(1.0d); + assertThat(reader.nextLong()).isEqualTo(-1L); + assertThat(reader.nextInt()).isEqualTo(-1); + assertThat(reader.nextDouble()).isEqualTo(-1.0d); + try { + reader.nextInt(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextLong()).isEqualTo(Long.MIN_VALUE); + try { + reader.nextInt(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextLong()).isEqualTo(Long.MAX_VALUE); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void booleans() throws IOException { + JsonReader reader = newReader("[true,false]"); + reader.beginArray(); + assertThat(reader.nextBoolean()).isTrue(); + assertThat(reader.nextBoolean()).isFalse(); + reader.endArray(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void nextFailuresDoNotAdvance() throws IOException { + JsonReader reader = newReader("{\"a\":true}"); + reader.beginObject(); + try { + reader.nextString(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextName()).isEqualTo("a"); + try { + reader.nextName(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.beginArray(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.endArray(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.beginObject(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.endObject(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextBoolean()).isTrue(); + try { + reader.nextString(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.nextName(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.beginArray(); + fail(); + } catch (JsonDataException expected) { + } + try { + reader.endArray(); + fail(); + } catch (JsonDataException expected) { + } + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + reader.close(); + } + + @Test public void integerMismatchWithDoubleDoesNotAdvance() throws IOException { + assumeTrue(factory != JsonReaderFactory.JSON_OBJECT); // TODO(jwilson): fix precision checks. + + JsonReader reader = newReader("[1.5]"); + reader.beginArray(); + try { + reader.nextInt(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextDouble()).isEqualTo(1.5d); + reader.endArray(); + } + + @Test public void integerMismatchWithLongDoesNotAdvance() throws IOException { + assumeTrue(factory != JsonReaderFactory.JSON_OBJECT); // TODO(jwilson): fix precision checks. + + JsonReader reader = newReader("[9223372036854775807]"); + reader.beginArray(); + try { + reader.nextInt(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextLong()).isEqualTo(9223372036854775807L); + reader.endArray(); + } + + @Test public void longMismatchWithDoubleDoesNotAdvance() throws IOException { + assumeTrue(factory != JsonReaderFactory.JSON_OBJECT); // TODO(jwilson): fix precision checks. + + JsonReader reader = newReader("[1.5]"); + reader.beginArray(); + try { + reader.nextLong(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextDouble()).isEqualTo(1.5d); + reader.endArray(); + } + + @Test public void stringNullIsNotNull() throws IOException { + JsonReader reader = newReader("[\"null\"]"); + reader.beginArray(); + try { + reader.nextNull(); + fail(); + } catch (JsonDataException expected) { + } + } + + @Test public void nullLiteralIsNotAString() throws IOException { + JsonReader reader = newReader("[null]"); + reader.beginArray(); + try { + reader.nextString(); + fail(); + } catch (JsonDataException expected) { + } + } + + @Test public void topLevelValueTypes() throws IOException { + JsonReader reader1 = newReader("true"); + assertThat(reader1.nextBoolean()).isTrue(); + assertThat(reader1.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + + JsonReader reader2 = newReader("false"); + assertThat(reader2.nextBoolean()).isFalse(); + assertThat(reader2.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + + JsonReader reader3 = newReader("null"); + assertThat(reader3.nextNull()).isNull(); + assertThat(reader3.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + + JsonReader reader4 = newReader("123"); + assertThat(reader4.nextInt()).isEqualTo(123); + assertThat(reader4.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + + JsonReader reader5 = newReader("123.4"); + assertThat(reader5.nextDouble()).isEqualTo(123.4); + assertThat(reader5.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + + JsonReader reader6 = newReader("\"a\""); + assertThat(reader6.nextString()).isEqualTo("a"); + assertThat(reader6.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void topLevelValueTypeWithSkipValue() throws IOException { + JsonReader reader = newReader("true"); + reader.skipValue(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void deeplyNestedArrays() throws IOException { + JsonReader reader = newReader("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); + for (int i = 0; i < 31; i++) { + reader.beginArray(); + } + assertThat(reader.getPath()).isEqualTo("$[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]" + + "[0][0][0][0][0][0][0][0][0][0][0][0][0]"); + for (int i = 0; i < 31; i++) { + reader.endArray(); + } + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void deeplyNestedObjects() throws IOException { + // Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 31 levels deep. + String array = "{\"a\":%s}"; + String json = "true"; + for (int i = 0; i < 31; i++) { + json = String.format(array, json); + } + + JsonReader reader = newReader(json); + for (int i = 0; i < 31; i++) { + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + } + assertThat(reader.getPath()) + .isEqualTo("$.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a"); + assertThat(reader.nextBoolean()).isTrue(); + for (int i = 0; i < 31; i++) { + reader.endObject(); + } + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipVeryLongUnquotedString() throws IOException { + JsonReader reader = newReader("[" + repeat('x', 8192) + "]"); + reader.setLenient(true); + reader.beginArray(); + reader.skipValue(); + reader.endArray(); + } + + @Test public void skipTopLevelUnquotedString() throws IOException { + JsonReader reader = newReader(repeat('x', 8192)); + reader.setLenient(true); + reader.skipValue(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void skipVeryLongQuotedString() throws IOException { + JsonReader reader = newReader("[\"" + repeat('x', 8192) + "\"]"); + reader.beginArray(); + reader.skipValue(); + reader.endArray(); + } + + @Test public void skipTopLevelQuotedString() throws IOException { + JsonReader reader = newReader("\"" + repeat('x', 8192) + "\""); + reader.setLenient(true); + reader.skipValue(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void stringAsNumberWithTruncatedExponent() throws IOException { + JsonReader reader = newReader("[123e]"); + reader.setLenient(true); + reader.beginArray(); + assertThat(reader.peek()).isEqualTo(STRING); + } + + @Test public void stringAsNumberWithDigitAndNonDigitExponent() throws IOException { + JsonReader reader = newReader("[123e4b]"); + reader.setLenient(true); + reader.beginArray(); + assertThat(reader.peek()).isEqualTo(STRING); + } + + @Test public void stringAsNumberWithNonDigitExponent() throws IOException { + JsonReader reader = newReader("[123eb]"); + reader.setLenient(true); + reader.beginArray(); + assertThat(reader.peek()).isEqualTo(STRING); + } + + @Test public void emptyStringName() throws IOException { + JsonReader reader = newReader("{\"\":true}"); + reader.setLenient(true); + assertThat(reader.peek()).isEqualTo(BEGIN_OBJECT); + reader.beginObject(); + assertThat(reader.peek()).isEqualTo(NAME); + assertThat(reader.nextName()).isEqualTo(""); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.BOOLEAN); + assertThat(reader.nextBoolean()).isTrue(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT); + reader.endObject(); + assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); + } + + @Test public void validEscapes() throws IOException { + JsonReader reader = newReader("[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]"); + reader.beginArray(); + assertThat(reader.nextString()).isEqualTo("\"\\/\b\f\n\r\t"); + } + + @Test public void selectName() throws IOException { + JsonReader.Options abc = JsonReader.Options.of("a", "b", "c"); + + JsonReader reader = newReader("{\"a\": 5, \"b\": 5, \"c\": 5, \"d\": 5}"); + reader.beginObject(); + assertEquals("$.", reader.getPath()); + + assertEquals(0, reader.selectName(abc)); + assertEquals("$.a", reader.getPath()); + assertEquals(5, reader.nextInt()); + assertEquals("$.a", reader.getPath()); + + assertEquals(1, reader.selectName(abc)); + assertEquals("$.b", reader.getPath()); + assertEquals(5, reader.nextInt()); + assertEquals("$.b", reader.getPath()); + + assertEquals(2, reader.selectName(abc)); + assertEquals("$.c", reader.getPath()); + assertEquals(5, reader.nextInt()); + assertEquals("$.c", reader.getPath()); + + // A missed selectName() doesn't advance anything, not even the path. + assertEquals(-1, reader.selectName(abc)); + assertEquals("$.c", reader.getPath()); + assertEquals(JsonReader.Token.NAME, reader.peek()); + + assertEquals("d", reader.nextName()); + assertEquals("$.d", reader.getPath()); + assertEquals(5, reader.nextInt()); + assertEquals("$.d", reader.getPath()); + + reader.endObject(); + } + + @Test public void selectString() throws IOException { + JsonReader.Options abc = JsonReader.Options.of("a", "b", "c"); + + JsonReader reader = newReader("[\"a\", \"b\", \"c\", \"d\"]"); + reader.beginArray(); + assertEquals("$[0]", reader.getPath()); + + assertEquals(0, reader.selectString(abc)); + assertEquals("$[1]", reader.getPath()); + + assertEquals(1, reader.selectString(abc)); + assertEquals("$[2]", reader.getPath()); + + assertEquals(2, reader.selectString(abc)); + assertEquals("$[3]", reader.getPath()); + + // A missed selectName() doesn't advance anything, not even the path. + assertEquals(-1, reader.selectString(abc)); + assertEquals("$[3]", reader.getPath()); + assertEquals(JsonReader.Token.STRING, reader.peek()); + + assertEquals("d", reader.nextString()); + assertEquals("$[4]", reader.getPath()); + + reader.endArray(); + } + + /** Select does match necessarily escaping. The decoded value is used in the path. */ + @Test public void selectNecessaryEscaping() throws IOException { + JsonReader.Options options = JsonReader.Options.of("\n", "\u0000", "\""); + + JsonReader reader = newReader("{\"\\n\": 5,\"\\u0000\": 5, \"\\\"\": 5}"); + reader.beginObject(); + assertEquals(0, reader.selectName(options)); + assertEquals(5, reader.nextInt()); + assertEquals("$.\n", reader.getPath()); + assertEquals(1, reader.selectName(options)); + assertEquals(5, reader.nextInt()); + assertEquals("$.\u0000", reader.getPath()); + assertEquals(2, reader.selectName(options)); + assertEquals(5, reader.nextInt()); + assertEquals("$.\"", reader.getPath()); + reader.endObject(); + } + + @Test public void stringToNumberCoersion() throws Exception { + JsonReader reader = newReader("[\"0\", \"9223372036854775807\", \"1.5\"]"); + reader.beginArray(); + assertThat(reader.nextInt()).isEqualTo(0); + assertThat(reader.nextLong()).isEqualTo(9223372036854775807L); + assertThat(reader.nextDouble()).isEqualTo(1.5d); + reader.endArray(); + } + + @Test public void unnecessaryPrecisionNumberCoersion() throws Exception { + JsonReader reader = newReader("[\"0.0\", \"9223372036854775807.0\"]"); + reader.beginArray(); + assertThat(reader.nextInt()).isEqualTo(0); + assertThat(reader.nextLong()).isEqualTo(9223372036854775807L); + reader.endArray(); + } + + @Test public void nanInfinityDoubleCoersion() throws Exception { + JsonReader reader = newReader("[\"NaN\", \"Infinity\", \"-Infinity\"]"); + reader.beginArray(); + reader.setLenient(true); + assertThat(reader.nextDouble()).isNaN(); + assertThat(reader.nextDouble()).isEqualTo(Double.POSITIVE_INFINITY); + assertThat(reader.nextDouble()).isEqualTo(Double.NEGATIVE_INFINITY); + reader.endArray(); + } + + @Test public void intMismatchWithStringDoesNotAdvance() throws Exception { + JsonReader reader = newReader("[\"a\"]"); + reader.beginArray(); + try { + reader.nextInt(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextString()).isEqualTo("a"); + reader.endArray(); + } + + @Test public void longMismatchWithStringDoesNotAdvance() throws Exception { + JsonReader reader = newReader("[\"a\"]"); + reader.beginArray(); + try { + reader.nextLong(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextString()).isEqualTo("a"); + reader.endArray(); + } + + @Test public void doubleMismatchWithStringDoesNotAdvance() throws Exception { + JsonReader reader = newReader("[\"a\"]"); + reader.beginArray(); + try { + reader.nextDouble(); + fail(); + } catch (JsonDataException expected) { + } + assertThat(reader.nextString()).isEqualTo("a"); + reader.endArray(); + } +} diff --git a/moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java b/moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java index 6bf30be..a76d058 100644 --- a/moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java +++ b/moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java @@ -15,6 +15,7 @@ */ package com.squareup.moshi; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; @@ -22,6 +23,8 @@ import java.util.List; import java.util.Map; import org.junit.Test; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -106,8 +109,7 @@ public final class ObjectJsonReaderTest { @Test public void nesting() throws Exception { List>>> root - = Collections.singletonList(Collections.singletonMap( - "a", Collections.singletonList(Collections.singletonMap("b", 1.5d)))); + = singletonList(singletonMap("a", singletonList(singletonMap("b", 1.5d)))); JsonReader reader = new ObjectJsonReader(root); assertThat(reader.hasNext()).isTrue(); @@ -158,7 +160,7 @@ public final class ObjectJsonReaderTest { } @Test public void promoteNameToValue() throws Exception { - Map root = Collections.singletonMap("a", "b"); + Map root = singletonMap("a", "b"); JsonReader reader = new ObjectJsonReader(root); reader.beginObject(); @@ -174,31 +176,32 @@ public final class ObjectJsonReaderTest { } @Test public void endArrayTooEarly() throws Exception { - JsonReader reader = new ObjectJsonReader(Collections.singletonList("s")); + JsonReader reader = new ObjectJsonReader(singletonList("s")); reader.beginArray(); try { reader.endArray(); fail(); } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Expected END_ARRAY but was STRING at path $[0]"); + assertThat(expected).hasMessage( + "Expected END_ARRAY but was s, a java.lang.String, at path $[0]"); } } @Test public void endObjectTooEarly() throws Exception { - JsonReader reader = new ObjectJsonReader(Collections.singletonMap("a", "b")); + JsonReader reader = new ObjectJsonReader(singletonMap("a", "b")); reader.beginObject(); try { reader.endObject(); fail(); } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Expected END_OBJECT but was NAME at path $."); + assertThat(expected).hasMessageStartingWith("Expected END_OBJECT but was a=b"); } } @Test public void unsupportedType() throws Exception { - JsonReader reader = new ObjectJsonReader(Collections.singletonList(new StringBuilder("x"))); + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("x"))); reader.beginArray(); try { @@ -206,12 +209,109 @@ public final class ObjectJsonReaderTest { fail(); } catch (JsonDataException expected) { assertThat(expected).hasMessage( - "Expected a JSON value but was a java.lang.StringBuilder at path $[0]"); + "Expected a JSON value but was x, a java.lang.StringBuilder, at path $[0]"); + } + } + + @Test public void unsupportedKeyType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonMap(new StringBuilder("x"), "y")); + + reader.beginObject(); + try { + reader.nextName(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected NAME but was x, a java.lang.StringBuilder, at path $."); + } + } + + @Test public void nullKey() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonMap(null, "y")); + + reader.beginObject(); + try { + reader.nextName(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage("Expected NAME but was null at path $."); + } + } + + @Test public void unexpectedIntType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("1"))); + reader.beginArray(); + try { + reader.nextInt(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected NUMBER but was 1, a java.lang.StringBuilder, at path $[0]"); + } + } + + @Test public void unexpectedLongType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("1"))); + reader.beginArray(); + try { + reader.nextLong(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected NUMBER but was 1, a java.lang.StringBuilder, at path $[0]"); + } + } + + @Test public void unexpectedDoubleType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("1"))); + reader.beginArray(); + try { + reader.nextDouble(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected NUMBER but was 1, a java.lang.StringBuilder, at path $[0]"); + } + } + + @Test public void unexpectedStringType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("s"))); + reader.beginArray(); + try { + reader.nextString(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected STRING but was s, a java.lang.StringBuilder, at path $[0]"); + } + } + + @Test public void unexpectedBooleanType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("true"))); + reader.beginArray(); + try { + reader.nextBoolean(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected BOOLEAN but was true, a java.lang.StringBuilder, at path $[0]"); + } + } + + @Test public void unexpectedNullType() throws Exception { + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("null"))); + reader.beginArray(); + try { + reader.nextNull(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Expected NULL but was null, a java.lang.StringBuilder, at path $[0]"); } } @Test public void skipRoot() throws Exception { - JsonReader reader = new ObjectJsonReader(Collections.singletonList(new StringBuilder("x"))); + JsonReader reader = new ObjectJsonReader(singletonList(new StringBuilder("x"))); reader.skipValue(); assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT); } @@ -292,7 +392,7 @@ public final class ObjectJsonReaderTest { } @Test public void failOnUnknown() throws Exception { - JsonReader reader = new ObjectJsonReader(Collections.singletonList("a")); + JsonReader reader = new ObjectJsonReader(singletonList("a")); reader.setFailOnUnknown(true); reader.beginArray(); @@ -306,7 +406,7 @@ public final class ObjectJsonReaderTest { @Test public void close() throws Exception { try { - JsonReader reader = new ObjectJsonReader(Collections.singletonList("a")); + JsonReader reader = new ObjectJsonReader(singletonList("a")); reader.beginArray(); reader.close(); reader.nextString(); @@ -315,11 +415,48 @@ public final class ObjectJsonReaderTest { } try { - JsonReader reader = new ObjectJsonReader(Collections.singletonList("a")); + JsonReader reader = new ObjectJsonReader(singletonList("a")); reader.close(); reader.beginArray(); fail(); } catch (IllegalStateException expected) { } } + + @Test public void tooDeeplyNestedArrays() throws IOException { + Object root = Collections.emptyList(); + for (int i = 0; i < 32; i++) { + root = singletonList(root); + } + JsonReader reader = new ObjectJsonReader(root); + for (int i = 0; i < 31; i++) { + reader.beginArray(); + } + try { + reader.beginArray(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage("Nesting too deep at $[0][0][0][0][0][0][0][0][0][0][0][0][0]" + + "[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]"); + } + } + + @Test public void tooDeeplyNestedObjects() throws IOException { + Object root = Boolean.TRUE; + for (int i = 0; i < 32; i++) { + root = singletonMap("a", root); + } + JsonReader reader = new ObjectJsonReader(root); + for (int i = 0; i < 31; i++) { + reader.beginObject(); + assertThat(reader.nextName()).isEqualTo("a"); + } + try { + reader.beginObject(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage( + "Nesting too deep at $.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a."); + } + } } diff --git a/moshi/src/test/java/com/squareup/moshi/TestUtil.java b/moshi/src/test/java/com/squareup/moshi/TestUtil.java index b01afac..0a64af2 100644 --- a/moshi/src/test/java/com/squareup/moshi/TestUtil.java +++ b/moshi/src/test/java/com/squareup/moshi/TestUtil.java @@ -15,6 +15,7 @@ */ package com.squareup.moshi; +import java.util.Arrays; import okio.Buffer; final class TestUtil { @@ -23,6 +24,12 @@ final class TestUtil { return JsonReader.of(buffer); } + static String repeat(char c, int count) { + char[] array = new char[count]; + Arrays.fill(array, c); + return new String(array); + } + private TestUtil() { throw new AssertionError("No instances."); }