mirror of
https://github.com/fankes/moshi.git
synced 2025-10-19 16:09:21 +08:00
First step of a JsonReader that reads a DOM.
https://github.com/square/moshi/issues/89
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;
|
package com.squareup.moshi;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import okio.Buffer;
|
||||||
import org.junit.Test;
|
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.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assume.assumeTrue;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
public final class JsonReaderPathTest {
|
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 {
|
@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("$");
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
assertThat(reader.getPath()).isEqualTo("$.");
|
assertThat(reader.getPath()).isEqualTo("$.");
|
||||||
@@ -62,7 +98,7 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void arrayOfObjects() throws IOException {
|
@Test public void arrayOfObjects() throws IOException {
|
||||||
JsonReader reader = newReader("[{},{},{}]");
|
JsonReader reader = factory.newReader("[{},{},{}]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
assertThat(reader.getPath()).isEqualTo("$[0]");
|
assertThat(reader.getPath()).isEqualTo("$[0]");
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
@@ -82,7 +118,7 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void arrayOfArrays() throws IOException {
|
@Test public void arrayOfArrays() throws IOException {
|
||||||
JsonReader reader = newReader("[[],[],[]]");
|
JsonReader reader = factory.newReader("[[],[],[]]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
assertThat(reader.getPath()).isEqualTo("$[0]");
|
assertThat(reader.getPath()).isEqualTo("$[0]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
@@ -102,7 +138,7 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void objectPath() throws IOException {
|
@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("$");
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
|
||||||
reader.peek();
|
reader.peek();
|
||||||
@@ -142,7 +178,7 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void arrayPath() throws IOException {
|
@Test public void arrayPath() throws IOException {
|
||||||
JsonReader reader = newReader("[1,2]");
|
JsonReader reader = factory.newReader("[1,2]");
|
||||||
assertThat(reader.getPath()).isEqualTo("$");
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
|
||||||
reader.peek();
|
reader.peek();
|
||||||
@@ -172,7 +208,9 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
|
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
|
||||||
JsonReader reader = newReader("[][]");
|
assumeTrue(factory != Factory.JSON_OBJECT);
|
||||||
|
|
||||||
|
JsonReader reader = factory.newReader("[][]");
|
||||||
reader.setLenient(true);
|
reader.setLenient(true);
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
reader.endArray();
|
reader.endArray();
|
||||||
@@ -183,7 +221,7 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void skipArrayElements() throws IOException {
|
@Test public void skipArrayElements() throws IOException {
|
||||||
JsonReader reader = newReader("[1,2,3]");
|
JsonReader reader = factory.newReader("[1,2,3]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
@@ -191,14 +229,14 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void skipObjectNames() throws IOException {
|
@Test public void skipObjectNames() throws IOException {
|
||||||
JsonReader reader = newReader("{\"a\":1}");
|
JsonReader reader = factory.newReader("{\"a\":1}");
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
assertThat(reader.getPath()).isEqualTo("$.null");
|
assertThat(reader.getPath()).isEqualTo("$.null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void skipObjectValues() throws IOException {
|
@Test public void skipObjectValues() throws IOException {
|
||||||
JsonReader reader = newReader("{\"a\":1,\"b\":2}");
|
JsonReader reader = factory.newReader("{\"a\":1,\"b\":2}");
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
reader.nextName();
|
reader.nextName();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
@@ -208,7 +246,7 @@ public final class JsonReaderPathTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test public void skipNestedStructures() throws IOException {
|
@Test public void skipNestedStructures() throws IOException {
|
||||||
JsonReader reader = newReader("[[1,2,3],4]");
|
JsonReader reader = factory.newReader("[[1,2,3],4]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
assertThat(reader.getPath()).isEqualTo("$[1]");
|
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