First step of a JsonReader that reads a DOM.

https://github.com/square/moshi/issues/89
This commit is contained in:
jwilson
2017-01-17 00:26:12 -08:00
parent c0a05b56bd
commit 1be3e84733
3 changed files with 664 additions and 11 deletions

View File

@@ -0,0 +1,290 @@
/*
* 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.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* This class reads a JSON document by traversing a Java object comprising maps, lists, and JSON
* primitives. It does depth-first traversal keeping a stack starting with the root object. During
* traversal a stack tracks the current position in the document:
*
* <ul>
* <li>The next element to act upon is on the top of the stack.
* <li>When the top of the stack is a {@link List}, calling {@link #beginArray()} replaces the
* list with a {@link ListIterator}. The first element of the iterator is pushed on top of the
* iterator.
* <li>Similarly, when the top of the stack is a {@link Map}, calling {@link #beginObject()}
* replaces the map with an {@link Iterator} of its entries. The first element of the iterator
* is pushed on top of the iterator.
* <li>When the top of the stack is a {@link Map.Entry}, calling {@link #nextName()} returns the
* entry's key and replaces the entry with its value on the stack.
* <li>When an element is consumed it is popped. If the new top of the stack has a non-exhausted
* iterator, the next element of that iterator is pushed.
* <li>If the top of the stack is an exhausted iterator, calling {@link #endArray} or {@link
* #endObject} will pop it.
* </ul>
*/
final class ObjectJsonReader extends JsonReader {
/** Sentinel object pushed on {@link #stack} when the reader is closed. */
private static final Object JSON_READER_CLOSED = new Object();
private int stackSize = 0;
private final Object[] stack = new Object[32];
private final int[] scopes = new int[32];
private final String[] pathNames = new String[32];
private final int[] pathIndices = new int[32];
private boolean lenient;
private boolean failOnUnknown;
public ObjectJsonReader(Object root) {
scopes[stackSize] = JsonScope.NONEMPTY_DOCUMENT;
stack[stackSize++] = root;
}
@Override public void setLenient(boolean lenient) {
this.lenient = lenient;
}
@Override public boolean isLenient() {
return lenient;
}
@Override public void setFailOnUnknown(boolean failOnUnknown) {
this.failOnUnknown = failOnUnknown;
}
@Override public boolean failOnUnknown() {
return failOnUnknown;
}
@Override public void beginArray() throws IOException {
List<?> peeked = require(List.class, Token.BEGIN_ARRAY);
ListIterator<?> iterator = peeked.listIterator();
stack[stackSize - 1] = iterator;
scopes[stackSize - 1] = JsonScope.EMPTY_ARRAY;
pathIndices[stackSize - 1] = 0;
// If the iterator isn't empty push its first value onto the stack.
if (iterator.hasNext()) {
stack[stackSize] = iterator.next();
stackSize++;
}
}
@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());
}
remove();
}
@Override public void beginObject() throws IOException {
Map<?, ?> peeked = require(Map.class, Token.BEGIN_OBJECT);
Iterator<?> iterator = peeked.entrySet().iterator();
stack[stackSize - 1] = iterator;
scopes[stackSize - 1] = JsonScope.EMPTY_OBJECT;
// If the iterator isn't empty push its first value onto the stack.
if (iterator.hasNext()) {
stack[stackSize] = iterator.next();
stackSize++;
}
}
@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());
}
pathNames[stackSize - 1] = null;
remove();
}
@Override public boolean hasNext() throws IOException {
// TODO(jwilson): this is consistent with BufferedSourceJsonReader but it doesn't make sense.
if (stackSize == 0) return true;
Object peeked = stack[stackSize - 1];
return !(peeked instanceof Iterator) || ((Iterator) peeked).hasNext();
}
@Override public Token peek() throws IOException {
if (stackSize == 0) return Token.END_DOCUMENT;
// If the top of the stack is an iterator, take its first element and push it on the stack.
Object peeked = stack[stackSize - 1];
if (peeked instanceof ListIterator) return Token.END_ARRAY;
if (peeked instanceof Iterator) return Token.END_OBJECT;
if (peeked instanceof List) return Token.BEGIN_ARRAY;
if (peeked instanceof Map) return Token.BEGIN_OBJECT;
if (peeked instanceof Map.Entry) return Token.NAME;
if (peeked instanceof String) return Token.STRING;
if (peeked instanceof Boolean) return Token.BOOLEAN;
if (peeked instanceof Number) return Token.NUMBER;
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());
}
@Override public String nextName() throws IOException {
Object 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();
pathNames[stackSize - 2] = result;
return result;
}
@Override int selectName(Options options) throws IOException {
throw new UnsupportedOperationException();
}
@Override public String nextString() throws IOException {
String peeked = require(String.class, Token.STRING);
remove();
return peeked;
}
@Override int selectString(Options options) throws IOException {
throw new UnsupportedOperationException();
}
@Override public boolean nextBoolean() throws IOException {
Boolean peeked = require(Boolean.class, Token.BOOLEAN);
remove();
return peeked;
}
@Override public <T> T nextNull() throws IOException {
require(Void.class, Token.NULL);
remove();
return null;
}
@Override public double nextDouble() throws IOException {
Number peeked = require(Number.class, Token.NUMBER);
remove();
return peeked.doubleValue(); // TODO(jwilson): precision check?
}
@Override public long nextLong() throws IOException {
Number peeked = require(Number.class, Token.NUMBER);
remove();
return peeked.longValue(); // TODO(jwilson): precision check?
}
@Override public int nextInt() throws IOException {
Number peeked = require(Number.class, Token.NUMBER);
remove();
return peeked.intValue(); // TODO(jwilson): precision check?
}
@Override public void skipValue() throws IOException {
if (failOnUnknown) {
throw new JsonDataException("Cannot skip unexpected " + peek() + " at " + getPath());
}
// If this element is in an object clear out the key.
if (stackSize > 1) {
pathNames[stackSize - 2] = "null";
}
Object skipped = stackSize != 0 ? stack[stackSize - 1] : null;
if (skipped instanceof Map.Entry) {
// We're skipping a name. Promote the map entry's value.
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) stack[stackSize - 1];
stack[stackSize - 1] = entry.getValue();
} else if (stackSize > 0) {
// We're skipping a value.
remove();
}
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices);
}
@Override void promoteNameToValue() throws IOException {
Object peeked = require(Map.Entry.class, Token.NAME);
stackSize++;
stack[stackSize - 2] = ((Map.Entry<?, ?>) peeked).getValue();
stack[stackSize - 1] = ((Map.Entry<?, ?>) peeked).getKey();
}
@Override public void close() throws IOException {
Arrays.fill(stack, 0, stackSize, null);
stack[0] = JSON_READER_CLOSED;
scopes[0] = JsonScope.CLOSED;
stackSize = 1;
}
/**
* 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.
*/
private <T> T require(Class<T> type, Token expected) throws IOException {
Object peeked = (stackSize != 0 ? stack[stackSize - 1] : null);
if (type.isInstance(peeked)) {
return type.cast(peeked);
}
if (peeked == null && expected == Token.NULL) {
return null;
}
if (peeked == JSON_READER_CLOSED) {
throw new IllegalStateException("JsonReader is closed");
}
throw new JsonDataException(
"Expected " + expected + " but was " + peek() + " at path " + getPath());
}
/**
* Removes a value and prepares for the next. If we're iterating a map or list this advances the
* iterator.
*/
private void remove() {
stackSize--;
stack[stackSize] = null;
scopes[stackSize] = 0;
// If we're iterating an array or an object push its next element on to the stack.
if (stackSize > 0) {
pathIndices[stackSize - 1]++;
Object parent = stack[stackSize - 1];
if (parent instanceof Iterator && ((Iterator<?>) parent).hasNext()) {
stack[stackSize] = ((Iterator<?>) parent).next();
stackSize++;
}
}
}
}

View File

@@ -16,14 +16,50 @@
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;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import static com.squareup.moshi.TestUtil.newReader;
import static org.assertj.core.api.Assertions.assertThat;
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;
}
@Parameters(name = "{0}")
public static List<Object[]> parameters() {
return Arrays.asList(
new Object[] { Factory.BUFFERED_SOURCE},
new Object[] { Factory.JSON_OBJECT});
}
@Parameter public Factory factory;
@Test public void path() throws IOException {
JsonReader reader = newReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}");
JsonReader reader = factory.newReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}");
assertThat(reader.getPath()).isEqualTo("$");
reader.beginObject();
assertThat(reader.getPath()).isEqualTo("$.");
@@ -62,7 +98,7 @@ public final class JsonReaderPathTest {
}
@Test public void arrayOfObjects() throws IOException {
JsonReader reader = newReader("[{},{},{}]");
JsonReader reader = factory.newReader("[{},{},{}]");
reader.beginArray();
assertThat(reader.getPath()).isEqualTo("$[0]");
reader.beginObject();
@@ -82,7 +118,7 @@ public final class JsonReaderPathTest {
}
@Test public void arrayOfArrays() throws IOException {
JsonReader reader = newReader("[[],[],[]]");
JsonReader reader = factory.newReader("[[],[],[]]");
reader.beginArray();
assertThat(reader.getPath()).isEqualTo("$[0]");
reader.beginArray();
@@ -102,7 +138,7 @@ public final class JsonReaderPathTest {
}
@Test public void objectPath() throws IOException {
JsonReader reader = newReader("{\"a\":1,\"b\":2}");
JsonReader reader = factory.newReader("{\"a\":1,\"b\":2}");
assertThat(reader.getPath()).isEqualTo("$");
reader.peek();
@@ -142,7 +178,7 @@ public final class JsonReaderPathTest {
}
@Test public void arrayPath() throws IOException {
JsonReader reader = newReader("[1,2]");
JsonReader reader = factory.newReader("[1,2]");
assertThat(reader.getPath()).isEqualTo("$");
reader.peek();
@@ -172,7 +208,9 @@ public final class JsonReaderPathTest {
}
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
JsonReader reader = newReader("[][]");
assumeTrue(factory != Factory.JSON_OBJECT);
JsonReader reader = factory.newReader("[][]");
reader.setLenient(true);
reader.beginArray();
reader.endArray();
@@ -183,7 +221,7 @@ public final class JsonReaderPathTest {
}
@Test public void skipArrayElements() throws IOException {
JsonReader reader = newReader("[1,2,3]");
JsonReader reader = factory.newReader("[1,2,3]");
reader.beginArray();
reader.skipValue();
reader.skipValue();
@@ -191,14 +229,14 @@ public final class JsonReaderPathTest {
}
@Test public void skipObjectNames() throws IOException {
JsonReader reader = newReader("{\"a\":1}");
JsonReader reader = factory.newReader("{\"a\":1}");
reader.beginObject();
reader.skipValue();
assertThat(reader.getPath()).isEqualTo("$.null");
}
@Test public void skipObjectValues() throws IOException {
JsonReader reader = newReader("{\"a\":1,\"b\":2}");
JsonReader reader = factory.newReader("{\"a\":1,\"b\":2}");
reader.beginObject();
reader.nextName();
reader.skipValue();
@@ -208,7 +246,7 @@ public final class JsonReaderPathTest {
}
@Test public void skipNestedStructures() throws IOException {
JsonReader reader = newReader("[[1,2,3],4]");
JsonReader reader = factory.newReader("[[1,2,3],4]");
reader.beginArray();
reader.skipValue();
assertThat(reader.getPath()).isEqualTo("$[1]");

View File

@@ -0,0 +1,325 @@
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
public final class ObjectJsonReaderTest {
@Test public void array() throws Exception {
List<Object> root = new ArrayList<>();
root.add("s");
root.add(1.5d);
root.add(true);
root.add(null);
JsonReader reader = new ObjectJsonReader(root);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_ARRAY);
reader.beginArray();
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
assertThat(reader.nextString()).isEqualTo("s");
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NUMBER);
assertThat(reader.nextDouble()).isEqualTo(1.5d);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BOOLEAN);
assertThat(reader.nextBoolean()).isEqualTo(true);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NULL);
assertThat(reader.nextNull()).isNull();
assertThat(reader.hasNext()).isFalse();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_ARRAY);
reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void object() throws Exception {
Map<String, Object> root = new LinkedHashMap<>();
root.put("a", "s");
root.put("b", 1.5d);
root.put("c", true);
root.put("d", null);
JsonReader reader = new ObjectJsonReader(root);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_OBJECT);
reader.beginObject();
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
assertThat(reader.nextString()).isEqualTo("s");
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
assertThat(reader.nextName()).isEqualTo("b");
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NUMBER);
assertThat(reader.nextDouble()).isEqualTo(1.5d);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
assertThat(reader.nextName()).isEqualTo("c");
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BOOLEAN);
assertThat(reader.nextBoolean()).isEqualTo(true);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
assertThat(reader.nextName()).isEqualTo("d");
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NULL);
assertThat(reader.nextNull()).isNull();
assertThat(reader.hasNext()).isFalse();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT);
reader.endObject();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void nesting() throws Exception {
List<Map<String, List<Map<String, Double>>>> root
= Collections.singletonList(Collections.singletonMap(
"a", Collections.singletonList(Collections.singletonMap("b", 1.5d))));
JsonReader reader = new ObjectJsonReader(root);
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_ARRAY);
reader.beginArray();
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_OBJECT);
reader.beginObject();
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_ARRAY);
reader.beginArray();
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.BEGIN_OBJECT);
reader.beginObject();
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NAME);
assertThat(reader.nextName()).isEqualTo("b");
assertThat(reader.hasNext()).isTrue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.NUMBER);
assertThat(reader.nextDouble()).isEqualTo(1.5d);
assertThat(reader.hasNext()).isFalse();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT);
reader.endObject();
assertThat(reader.hasNext()).isFalse();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_ARRAY);
reader.endArray();
assertThat(reader.hasNext()).isFalse();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT);
reader.endObject();
assertThat(reader.hasNext()).isFalse();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_ARRAY);
reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void promoteNameToValue() throws Exception {
Map<String, String> root = Collections.singletonMap("a", "b");
JsonReader reader = new ObjectJsonReader(root);
reader.beginObject();
reader.promoteNameToValue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
assertThat(reader.nextString()).isEqualTo("a");
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
assertThat(reader.nextString()).isEqualTo("b");
reader.endObject();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void endArrayTooEarly() throws Exception {
JsonReader reader = new ObjectJsonReader(Collections.singletonList("s"));
reader.beginArray();
try {
reader.endArray();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Expected END_ARRAY but was STRING at path $[0]");
}
}
@Test public void endObjectTooEarly() throws Exception {
JsonReader reader = new ObjectJsonReader(Collections.singletonMap("a", "b"));
reader.beginObject();
try {
reader.endObject();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Expected END_OBJECT but was NAME at path $.");
}
}
@Test public void unsupportedType() throws Exception {
JsonReader reader = new ObjectJsonReader(Collections.singletonList(new StringBuilder("x")));
reader.beginArray();
try {
reader.peek();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage(
"Expected a JSON value but was a java.lang.StringBuilder at path $[0]");
}
}
@Test public void skipRoot() throws Exception {
JsonReader reader = new ObjectJsonReader(Collections.singletonList(new StringBuilder("x")));
reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void skipListValue() throws Exception {
List<Object> root = new ArrayList<>();
root.add("a");
root.add("b");
root.add("c");
JsonReader reader = new ObjectJsonReader(root);
reader.beginArray();
assertThat(reader.getPath()).isEqualTo("$[0]");
assertThat(reader.nextString()).isEqualTo("a");
assertThat(reader.getPath()).isEqualTo("$[1]");
reader.skipValue();
assertThat(reader.getPath()).isEqualTo("$[2]");
assertThat(reader.nextString()).isEqualTo("c");
reader.endArray();
}
@Test public void skipObjectName() throws Exception {
Map<String, Object> root = new LinkedHashMap<>();
root.put("a", "s");
root.put("b", 1.5d);
root.put("c", true);
JsonReader reader = new ObjectJsonReader(root);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.getPath()).isEqualTo("$.a");
assertThat(reader.nextString()).isEqualTo("s");
assertThat(reader.getPath()).isEqualTo("$.a");
reader.skipValue();
assertThat(reader.getPath()).isEqualTo("$.null");
assertThat(reader.nextDouble()).isEqualTo(1.5d);
assertThat(reader.getPath()).isEqualTo("$.null");
assertThat(reader.nextName()).isEqualTo("c");
assertThat(reader.getPath()).isEqualTo("$.c");
assertThat(reader.nextBoolean()).isEqualTo(true);
assertThat(reader.getPath()).isEqualTo("$.c");
reader.endObject();
}
@Test public void skipObjectValue() throws Exception {
Map<String, Object> root = new LinkedHashMap<>();
root.put("a", "s");
root.put("b", 1.5d);
root.put("c", true);
JsonReader reader = new ObjectJsonReader(root);
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("a");
assertThat(reader.getPath()).isEqualTo("$.a");
assertThat(reader.nextString()).isEqualTo("s");
assertThat(reader.getPath()).isEqualTo("$.a");
assertThat(reader.nextName()).isEqualTo("b");
assertThat(reader.getPath()).isEqualTo("$.b");
reader.skipValue();
assertThat(reader.getPath()).isEqualTo("$.null");
assertThat(reader.nextName()).isEqualTo("c");
assertThat(reader.getPath()).isEqualTo("$.c");
assertThat(reader.nextBoolean()).isEqualTo(true);
assertThat(reader.getPath()).isEqualTo("$.c");
reader.endObject();
}
@Test public void failOnUnknown() throws Exception {
JsonReader reader = new ObjectJsonReader(Collections.singletonList("a"));
reader.setFailOnUnknown(true);
reader.beginArray();
try {
reader.skipValue();
fail();
} catch (JsonDataException expected) {
assertThat(expected).hasMessage("Cannot skip unexpected STRING at $[0]");
}
}
@Test public void close() throws Exception {
try {
JsonReader reader = new ObjectJsonReader(Collections.singletonList("a"));
reader.beginArray();
reader.close();
reader.nextString();
fail();
} catch (IllegalStateException expected) {
}
try {
JsonReader reader = new ObjectJsonReader(Collections.singletonList("a"));
reader.close();
reader.beginArray();
fail();
} catch (IllegalStateException expected) {
}
}
}