mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
Merge pull request #222 from square/jwilson.0117.object_json_reader
First step of a JsonReader that reads a DOM.
This commit is contained in:
290
moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java
Normal file
290
moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java
Normal 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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]");
|
||||
|
325
moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java
Normal file
325
moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user