This commit is contained in:
Jake Wharton 2016-02-12 15:50:57 -05:00
parent 33e1fb1bb5
commit b37e7c2dc4
6 changed files with 1245 additions and 14 deletions

View file

@ -19,6 +19,7 @@ import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import okio.Buffer;
import okio.BufferedSink;
import okio.BufferedSource;
@ -30,13 +31,21 @@ public abstract class JsonAdapter<T> {
public abstract T fromJson(JsonReader reader) throws IOException;
public final T fromJson(BufferedSource source) throws IOException {
return fromJson(JsonReader.of(source));
return fromJson(new BufferedSourceJsonReader(source));
}
public final T fromJson(String string) throws IOException {
return fromJson(new Buffer().writeUtf8(string));
}
public final T fromJsonTree(Object o) {
try {
return fromJson(new ObjectJsonReader(o));
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
public abstract void toJson(JsonWriter writer, T value) throws IOException;
public final void toJson(BufferedSink sink, T value) throws IOException {
@ -54,6 +63,16 @@ public abstract class JsonAdapter<T> {
return buffer.readUtf8();
}
public final Object toJsonTree(T value) {
AtomicReference<Object> sink = new AtomicReference<>();
try {
toJson(new ObjectJsonWriter(sink), value);
} catch (IOException e) {
throw new IllegalStateException(e);
}
return sink.get();
}
/**
* Returns a JSON adapter equal to this JSON adapter, but with support for reading and writing
* nulls.

View file

@ -0,0 +1,551 @@
/*
* Copyright (C) 2016 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.Iterator;
import java.util.Map;
final class ObjectJsonReader extends JsonReader {
private static final int PEEKED_NONE = 0;
private static final int PEEKED_MAP = 1;
private static final int PEEKED_MAP_ENTRY = 2;
private static final int PEEKED_MAP_ITERATOR_EXHAUSTED = 3;
private static final int PEEKED_LIST = 4;
private static final int PEEKED_LIST_ITERATOR_EXHAUSTED = 5;
private static final int PEEKED_BOOLEAN = 6;
private static final int PEEKED_NULL = 7;
private static final int PEEKED_STRING = 8;
private static final int PEEKED_INT = 9;
private static final int PEEKED_LONG = 10;
private static final int PEEKED_FLOAT = 11;
private static final int PEEKED_DOUBLE = 12;
private static final int PEEKED_DONE = 13;
/** True to accept non-spec compliant JSON */
private boolean lenient = false;
/** True to throw a {@link JsonDataException} on any attempt to call {@link #skipValue()}. */
private boolean failOnUnknown = false;
private int peeked = PEEKED_NONE;
/*
* The nesting stack. Using a manual array rather than an ArrayList saves 20%.
*/
private int[] stack = new int[32];
private int stackSize = 0;
{
stack[stackSize++] = JsonScope.EMPTY_DOCUMENT;
}
private String[] pathNames = new String[32];
private int[] pathIndices = new int[32];
private final Object source;
private Object[] objects = new Object[32];
private int objectsSize = 1;
ObjectJsonReader(Object source) {
this.source = source;
this.objects[0] = source;
}
@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;
}
private void pushObject(Object newTop) {
if (objectsSize == objects.length) {
Object[] newObjects = new Object[objectsSize * 2];
System.arraycopy(objects, 0, newObjects, 0, objectsSize);
objects = newObjects;
}
objects[objectsSize++] = newTop;
}
private Object popObject() {
Object object = objects[objectsSize - 1];
objects[objectsSize - 1] = null; // Free the object so that it can be garbage collected!
objectsSize--;
return object;
}
@Override public void beginArray() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_LIST) {
Iterable<?> iterable = (Iterable<?>) popObject();
pushObject(iterable.iterator());
push(JsonScope.EMPTY_ARRAY);
pathIndices[stackSize - 1] = 0;
peeked = PEEKED_NONE;
} else {
throw new JsonDataException("Expected BEGIN_ARRAY but was " + peek()
+ " at path " + getPath());
}
}
@Override public void endArray() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_LIST_ITERATOR_EXHAUSTED) {
popObject();
stackSize--;
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new JsonDataException("Expected END_ARRAY but was " + peek()
+ " at path " + getPath());
}
}
@Override public void beginObject() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_MAP) {
Map<?, ?> map = (Map<?, ?>) popObject();
pushObject(map.entrySet().iterator());
push(JsonScope.EMPTY_OBJECT);
peeked = PEEKED_NONE;
} else {
throw new JsonDataException("Expected BEGIN_OBJECT but was " + peek()
+ " at path " + getPath());
}
}
@Override public void endObject() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_MAP_ITERATOR_EXHAUSTED) {
popObject();
stackSize--;
pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected!
pathIndices[stackSize - 1]++;
peeked = PEEKED_NONE;
} else {
throw new JsonDataException("Expected END_OBJECT but was " + peek()
+ " at path " + getPath());
}
}
@Override public boolean hasNext() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
return p != PEEKED_LIST_ITERATOR_EXHAUSTED && p != PEEKED_MAP_ITERATOR_EXHAUSTED;
}
@Override public Token peek() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
switch (p) {
case PEEKED_MAP:
return Token.BEGIN_OBJECT;
case PEEKED_MAP_ITERATOR_EXHAUSTED:
return Token.END_OBJECT;
case PEEKED_LIST:
return Token.BEGIN_ARRAY;
case PEEKED_LIST_ITERATOR_EXHAUSTED:
return Token.END_ARRAY;
case PEEKED_MAP_ENTRY:
return Token.NAME;
case PEEKED_BOOLEAN:
return Token.BOOLEAN;
case PEEKED_NULL:
return Token.NULL;
case PEEKED_STRING:
return Token.STRING;
case PEEKED_INT:
case PEEKED_LONG:
case PEEKED_FLOAT:
case PEEKED_DOUBLE:
return Token.NUMBER;
case PEEKED_DONE:
return Token.END_DOCUMENT;
default:
throw new AssertionError();
}
}
private int doPeek() {
int peekStack = stack[stackSize - 1];
if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
Iterator<?> iterator = (Iterator<?>) objects[objectsSize - 1];
if (!iterator.hasNext()) {
return peeked = PEEKED_LIST_ITERATOR_EXHAUSTED;
}
pushObject(iterator.next());
} else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
stack[stackSize - 1] = JsonScope.DANGLING_NAME;
Iterator<Map.Entry<?, ?>> iterator = (Iterator<Map.Entry<?, ?>>) objects[objectsSize - 1];
if (!iterator.hasNext()) {
return peeked = PEEKED_MAP_ITERATOR_EXHAUSTED;
}
pushObject(iterator.next());
return peeked = PEEKED_MAP_ENTRY;
} else if (peekStack == JsonScope.DANGLING_NAME) {
stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
} else if (peekStack == JsonScope.CLOSED) {
throw new IllegalStateException("JsonReader is closed");
}
if (objectsSize == 0) {
return peeked = PEEKED_DONE;
}
Object object = objects[objectsSize - 1];
if (object == null) {
return peeked = PEEKED_NULL;
}
if (object instanceof Boolean) {
return peeked = PEEKED_BOOLEAN;
}
if (object instanceof String) {
return peeked = PEEKED_STRING;
}
if (object instanceof Integer) {
return peeked = PEEKED_INT;
}
if (object instanceof Long) {
return peeked = PEEKED_LONG;
}
if (object instanceof Float) {
return peeked = PEEKED_FLOAT;
}
if (object instanceof Double) {
return peeked = PEEKED_DOUBLE;
}
if (object instanceof Iterable) {
return peeked = PEEKED_LIST;
}
if (object instanceof Map) {
return peeked = PEEKED_MAP;
}
throw syntaxError("Unrecognized type " + object.getClass().getName() + ": " + object);
}
@Override public String nextName() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
String result;
if (p == PEEKED_MAP_ENTRY) {
Map.Entry<?, ?> object = (Map.Entry<?, ?>) popObject();
result = (String) object.getKey();
pushObject(object.getValue());
} else {
throw new JsonDataException("Expected a name but was " + peek() + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathNames[stackSize - 1] = result;
return result;
}
@Override public String nextString() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
String result;
if (p == PEEKED_STRING
|| p == PEEKED_INT
|| p == PEEKED_LONG
|| p == PEEKED_FLOAT
|| p == PEEKED_DOUBLE) {
result = popObject().toString();
} else {
throw new JsonDataException("Expected a string but was " + peek() + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return result;
}
@Override public boolean nextBoolean() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
boolean result;
if (p == PEEKED_BOOLEAN) {
result = (boolean) popObject();
} else {
throw new JsonDataException("Expected a boolean but was " + peek() + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return result;
}
@Override public <T> T nextNull() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_NULL) {
popObject();
} else {
throw new JsonDataException("Expected null but was " + peek() + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return null;
}
@Override public double nextDouble() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
Object object = popObject();
double result;
if (p == PEEKED_INT) {
result = (int) object;
} else if (p == PEEKED_LONG) {
result = (long) object;
} else if (p == PEEKED_FLOAT) {
result = (float) object;
} else if (p == PEEKED_DOUBLE) {
result = (double) object;
} else if (p == PEEKED_STRING) {
String string = object.toString();
try {
result = Double.parseDouble(string);
} catch (NumberFormatException e) {
throw new JsonDataException(
"Expected a double but was " + string + " at path " + getPath());
}
} else {
throw new JsonDataException("Expected a double but was " + peek() + " at path " + getPath());
}
if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new IllegalStateException(
"JSON forbids NaN and infinities: " + result + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return result;
}
@Override public long nextLong() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
Object object = popObject();
long result;
if (p == PEEKED_INT) {
result = (int) object;
} else if (p == PEEKED_LONG) {
result = (long) object;
} else if (p == PEEKED_FLOAT) {
result = ((Float) object).longValue();
} else if (p == PEEKED_DOUBLE) {
result = ((Double) object).longValue();
} else if (p == PEEKED_STRING) {
String string = object.toString();
try {
result = Long.parseLong(string);
} catch (NumberFormatException ignored) {
double asDouble;
try {
asDouble = Double.parseDouble(string);
} catch (NumberFormatException e) {
throw new JsonDataException(
"Expected a long but was " + string + " at path " + getPath());
}
result = (long) asDouble;
if (result != asDouble) { // Make sure no precision was lost casting to 'long'.
throw new JsonDataException(
"Expected a long but was " + string + " at path " + getPath());
}
}
} else {
throw new JsonDataException("Expected a long but was " + peek() + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return result;
}
@Override public int nextInt() {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
Object object = popObject();
int result;
if (p == PEEKED_INT) {
result = (int) object;
} else if (p == PEEKED_LONG) {
long asLong = (long) object;
result = (int) asLong;
if (result != asLong) { // Make sure no precision was lost casting to 'int'.
throw new JsonDataException("Expected an int but was " + object + " at path " + getPath());
}
} else if (p == PEEKED_FLOAT) {
float asFloat = (float) object;
result = (int) asFloat;
if (result != asFloat) { // Make sure no precision was lost casting to 'int'.
throw new JsonDataException("Expected an int but was " + object + " at path " + getPath());
}
} else if (p == PEEKED_DOUBLE) {
double asDouble = (double) object;
result = (int) asDouble;
if (result != asDouble) { // Make sure no precision was lost casting to 'int'.
throw new JsonDataException("Expected an int but was " + object + " at path " + getPath());
}
} else if (p == PEEKED_STRING) {
String string = object.toString();
try {
result = Integer.parseInt(string);
} catch (NumberFormatException ignored) {
double asDouble;
try {
asDouble = Double.parseDouble(string);
} catch (NumberFormatException e) {
throw new JsonDataException(
"Expected an int but was " + string + " at path " + getPath());
}
result = (int) asDouble;
if (result != asDouble) { // Make sure no precision was lost casting to 'int'.
throw new JsonDataException(
"Expected an int but was " + string + " at path " + getPath());
}
}
} else {
throw new JsonDataException("Expected an int but was " + peek() + " at path " + getPath());
}
peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++;
return result;
}
@Override public void close() {
peeked = PEEKED_NONE;
stack[0] = JsonScope.CLOSED;
stackSize = 1;
objects = null;
objectsSize = -1;
}
@Override public void skipValue() {
if (failOnUnknown) {
throw new JsonDataException("Cannot skip unexpected " + peek() + " at " + getPath());
}
int count = 0;
do {
int p = peeked;
if (p == PEEKED_NONE) {
p = doPeek();
}
if (p == PEEKED_LIST_ITERATOR_EXHAUSTED) {
stackSize--;
count--;
} else if (p == PEEKED_MAP_ITERATOR_EXHAUSTED) {
stackSize--;
count--;
} else {
popObject();
}
peeked = PEEKED_NONE;
} while (count != 0);
pathIndices[stackSize - 1]++;
pathNames[stackSize - 1] = "null";
}
private void push(int newTop) {
if (stackSize == stack.length) {
int[] newStack = new int[stackSize * 2];
int[] newPathIndices = new int[stackSize * 2];
String[] newPathNames = new String[stackSize * 2];
System.arraycopy(stack, 0, newStack, 0, stackSize);
System.arraycopy(pathIndices, 0, newPathIndices, 0, stackSize);
System.arraycopy(pathNames, 0, newPathNames, 0, stackSize);
stack = newStack;
pathIndices = newPathIndices;
pathNames = newPathNames;
}
stack[stackSize++] = newTop;
}
@Override public String toString() {
return "JsonReader(" + source + ")";
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, stack, pathNames, pathIndices);
}
/**
* Throws a new IO exception with the given message and a context snippet
* with this reader's content.
*/
private IllegalStateException syntaxError(String message) {
return new IllegalStateException(message + " at path " + getPath());
}
@Override void promoteNameToValue() {
if (hasNext()) {
pushObject(nextName());
peeked = PEEKED_STRING;
}
}
}

View file

@ -0,0 +1,263 @@
/*
* Copyright (C) 2016 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.ArrayList;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicReference;
import static com.squareup.moshi.JsonScope.DANGLING_NAME;
import static com.squareup.moshi.JsonScope.EMPTY_ARRAY;
import static com.squareup.moshi.JsonScope.EMPTY_DOCUMENT;
import static com.squareup.moshi.JsonScope.EMPTY_OBJECT;
import static com.squareup.moshi.JsonScope.NONEMPTY_ARRAY;
import static com.squareup.moshi.JsonScope.NONEMPTY_DOCUMENT;
import static com.squareup.moshi.JsonScope.NONEMPTY_OBJECT;
public final class ObjectJsonWriter extends JsonWriter {
private final AtomicReference<Object> sink;
private int[] stack = new int[32];
private int stackSize = 0;
{
push(EMPTY_DOCUMENT);
}
private String[] pathNames = new String[32];
private int[] pathIndices = new int[32];
private Object[] objects = new Object[32];
private int objectsSize = 1;
private boolean lenient;
private String deferredName;
private boolean serializeNulls;
private boolean promoteNameToValue;
public ObjectJsonWriter(AtomicReference<Object> sink) {
if (sink == null) throw new NullPointerException("sink == null");
this.sink = sink;
}
private void pushObject(Object newTop) {
if (objectsSize == objects.length) {
Object[] newObjects = new Object[objectsSize * 2];
System.arraycopy(objects, 0, newObjects, 0, objectsSize);
objects = newObjects;
}
objects[objectsSize++] = newTop;
}
private Object popObject() {
Object object = objects[objectsSize - 1];
objects[objectsSize - 1] = null; // Free the object so that it can be garbage collected!
objectsSize--;
return object;
}
@Override public void setIndent(String indent) {
// Ignored
}
@Override public final void setLenient(boolean lenient) {
this.lenient = lenient;
}
@Override public boolean isLenient() {
return lenient;
}
@Override public final void setSerializeNulls(boolean serializeNulls) {
this.serializeNulls = serializeNulls;
}
@Override public final boolean getSerializeNulls() {
return serializeNulls;
}
@Override public JsonWriter beginArray() {
pushObject(new LinkedHashMap<String, Object>());
return open(EMPTY_ARRAY);
}
@Override public JsonWriter endArray() {
return close(EMPTY_ARRAY, NONEMPTY_ARRAY);
}
@Override public JsonWriter beginObject() {
pushObject(new ArrayList<>());
return open(EMPTY_OBJECT);
}
@Override public JsonWriter endObject() {
return null;
}
/**
* Enters a new scope by appending any necessary whitespace and the given
* bracket.
*/
private JsonWriter open(int empty) {
beforeValue();
pathIndices[stackSize] = 0;
push(empty);
return this;
}
/**
* Closes the current scope by appending any necessary whitespace and the
* given bracket.
*/
private JsonWriter close(int empty, int nonempty) {
int context = peek();
if (context != nonempty && context != empty) {
throw new IllegalStateException("Nesting problem.");
}
if (deferredName != null) {
throw new IllegalStateException("Dangling name: " + deferredName);
}
stackSize--;
pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected!
pathIndices[stackSize - 1]++;
return this;
}
private void push(int newTop) {
if (stackSize == stack.length) {
int[] newStack = new int[stackSize * 2];
System.arraycopy(stack, 0, newStack, 0, stackSize);
stack = newStack;
}
stack[stackSize++] = newTop;
}
/**
* Returns the value on the top of the stack.
*/
private int peek() {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
return stack[stackSize - 1];
}
/**
* Replace the value on the top of the stack with the given value.
*/
private void replaceTop(int topOfStack) {
stack[stackSize - 1] = topOfStack;
}
@Override public JsonWriter name(String name) {
return null;
}
private void writeDeferredName() throws IOException {
if (deferredName != null) {
// TODO
deferredName = null;
}
}
@Override public JsonWriter value(String value) {
return null;
}
@Override public JsonWriter nullValue() {
return null;
}
@Override public JsonWriter value(boolean value) {
return null;
}
@Override public JsonWriter value(double value) {
return null;
}
@Override public JsonWriter value(long value) {
return null;
}
@Override public JsonWriter value(Number value) {
return null;
}
/**
* Inserts any necessary separators and whitespace before a literal value,
* inline array, or inline object. Also adjusts the stack to expect either a
* closing bracket or another element.
*/
@SuppressWarnings("fallthrough")
private void beforeValue() {
switch (peek()) {
case NONEMPTY_DOCUMENT:
if (!lenient) {
throw new IllegalStateException(
"JSON must have only one top-level value.");
}
// fall-through
case EMPTY_DOCUMENT: // first in document
replaceTop(NONEMPTY_DOCUMENT);
break;
case EMPTY_ARRAY: // first in array
replaceTop(NONEMPTY_ARRAY);
break;
case NONEMPTY_ARRAY: // another in array
break;
case DANGLING_NAME: // value for name
replaceTop(NONEMPTY_OBJECT);
break;
default:
throw new IllegalStateException("Nesting problem.");
}
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, stack, pathNames, pathIndices);
}
@Override void promoteNameToValue() {
int context = peek();
if (context != NONEMPTY_OBJECT && context != EMPTY_OBJECT) {
throw new IllegalStateException("Nesting problem.");
}
promoteNameToValue = true;
}
@Override public void close() throws IOException {
int size = stackSize;
if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) {
throw new IOException("Incomplete document");
}
stackSize = 0;
}
@Override public void flush() {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
}
}

View file

@ -719,30 +719,30 @@ public final class BufferedSourceJsonReaderTest {
}
@Test public void prematurelyClosed() throws IOException {
JsonReader reader1 = newReader("{\"a\":[]}");
reader1.beginObject();
reader1.close();
try {
JsonReader reader = newReader("{\"a\":[]}");
reader.beginObject();
reader.close();
reader.nextName();
reader1.nextName();
fail();
} catch (IllegalStateException expected) {
}
JsonReader reader2 = newReader("{\"a\":[]}");
reader2.close();
try {
JsonReader reader = newReader("{\"a\":[]}");
reader.close();
reader.beginObject();
reader2.beginObject();
fail();
} catch (IllegalStateException expected) {
}
JsonReader reader3 = newReader("{\"a\":true}");
reader3.beginObject();
reader3.nextName();
reader3.peek();
reader3.close();
try {
JsonReader reader = newReader("{\"a\":true}");
reader.beginObject();
reader.nextName();
reader.peek();
reader.close();
reader.nextBoolean();
reader3.nextBoolean();
fail();
} catch (IllegalStateException expected) {
}

View file

@ -0,0 +1,389 @@
package com.squareup.moshi;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import static com.squareup.moshi.JsonReader.Token.BEGIN_ARRAY;
import static com.squareup.moshi.JsonReader.Token.BEGIN_OBJECT;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
public final class ObjectJsonReaderTest {
@Test public void readArray() throws IOException {
JsonReader reader = new ObjectJsonReader(Arrays.asList(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 = new ObjectJsonReader(emptyList());
reader.beginArray();
assertThat(reader.hasNext()).isFalse();
reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void readObject() throws IOException {
Map<String, Object> object = new LinkedHashMap<>();
object.put("a", "android");
object.put("b", "banana");
JsonReader reader = new ObjectJsonReader(object);
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 = new ObjectJsonReader(Collections.emptyMap());
reader.beginObject();
assertThat(reader.hasNext()).isFalse();
reader.endObject();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void skipArray() throws IOException {
Map<String, Object> object = new LinkedHashMap<>();
object.put("a", Arrays.asList("one", "two", "three"));
object.put("b", 123);
JsonReader reader = new ObjectJsonReader(object);
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 IOException {
Map<String, Object> object = new LinkedHashMap<>();
object.put("a", Arrays.asList("one", "two", "three"));
object.put("b", 123);
JsonReader reader = new ObjectJsonReader(object);
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 IOException {
Map<String, Object> object = new LinkedHashMap<>();
object.put("a", Arrays.asList("one", "two", "three"));
object.put("b", 123);
JsonReader reader = new ObjectJsonReader(object);
reader.skipValue();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void skipObject() throws IOException {
Map<String, Object> object = new LinkedHashMap<>();
Map<String, Object> nestedObject = new LinkedHashMap<>();
nestedObject.put("c", emptyList());
nestedObject.put("d", Arrays.asList(true, true, Collections.emptyMap()));
object.put("a", nestedObject);
object.put("b", "banana");
JsonReader reader = new ObjectJsonReader(object);
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 IOException {
Map<String, Object> object = new LinkedHashMap<>();
object.put("one", singletonMap("num", 1));
object.put("two", singletonMap("num", 2));
object.put("three", singletonMap("num", 3));
JsonReader reader = new ObjectJsonReader(object);
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 {
Map<String, Object> object = new LinkedHashMap<>();
object.put("a", 123456789);
object.put("b", -123456789);
JsonReader reader = new ObjectJsonReader(object);
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 {
Map<String, Object> object = new LinkedHashMap<>();
object.put("a", Double.MIN_VALUE);
object.put("b", Double.MAX_VALUE);
JsonReader reader = new ObjectJsonReader(object);
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 {
Map<String, Object> object = Collections.<String, Object>singletonMap("a", 123);
JsonReader reader = new ObjectJsonReader(object);
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 = new ObjectJsonReader(Arrays.asList("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 {
Map<String, Object> object = new LinkedHashMap<>();
object.put("hello", true);
object.put("foo", Collections.singletonList("world"));
JsonReader reader = new ObjectJsonReader(object);
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 integerFromOtherNumberTypes() throws IOException {
JsonReader reader = new ObjectJsonReader(Arrays.asList(1, 1L, 1.0f, 1.0));
reader.beginArray();
assertThat(reader.nextInt()).isEqualTo(1);
assertThat(reader.nextInt()).isEqualTo(1);
assertThat(reader.nextInt()).isEqualTo(1);
assertThat(reader.nextInt()).isEqualTo(1);
reader.endArray();
}
@Test public void longFromOtherNumberTypes() throws IOException {
JsonReader reader = new ObjectJsonReader(Arrays.asList(1, 1L, 1.0f, 1.0));
reader.beginArray();
assertThat(reader.nextLong()).isEqualTo(1L);
assertThat(reader.nextLong()).isEqualTo(1L);
assertThat(reader.nextLong()).isEqualTo(1L);
assertThat(reader.nextLong()).isEqualTo(1L);
reader.endArray();
}
@Test public void doubleFromOtherNumberTypes() throws IOException {
JsonReader reader = new ObjectJsonReader(Arrays.asList(1, 1L, 1.0f, 1.0));
reader.beginArray();
assertThat(reader.nextDouble()).isEqualTo(1.0);
assertThat(reader.nextDouble()).isEqualTo(1.0);
assertThat(reader.nextDouble()).isEqualTo(1.0);
assertThat(reader.nextDouble()).isEqualTo(1.0);
reader.endArray();
}
@Test public void stringFromNumberTypes() throws IOException {
JsonReader reader = new ObjectJsonReader(Arrays.asList(1, 1L, 1.0f, 1.0));
reader.beginArray();
assertThat(reader.nextString()).isEqualTo("1");
assertThat(reader.nextString()).isEqualTo("1");
assertThat(reader.nextString()).isEqualTo("1.0");
assertThat(reader.nextString()).isEqualTo("1.0");
reader.endArray();
}
@Test public void prematurelyClosed() throws IOException {
JsonReader reader1 = new ObjectJsonReader(singletonMap("a", emptyList()));
reader1.beginObject();
reader1.close();
try {
reader1.nextName();
fail();
} catch (IllegalStateException expected) {
}
JsonReader reader2 = new ObjectJsonReader(singletonMap("a", emptyList()));
reader2.close();
try {
reader2.beginObject();
fail();
} catch (IllegalStateException expected) {
}
JsonReader reader3 = new ObjectJsonReader(singletonMap("a", true));
reader3.beginObject();
reader3.nextName();
reader3.peek();
reader3.close();
try {
reader3.nextBoolean();
fail();
} catch (IllegalStateException expected) {
}
}
@Test public void nextFailuresDoNotAdvance() throws IOException {
JsonReader reader = new ObjectJsonReader(singletonMap("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() {
}
@Test public void topLevelValueTypes() throws IOException {
JsonReader reader1 = new ObjectJsonReader(true);
assertThat(reader1.nextBoolean()).isTrue();
assertThat(reader1.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
JsonReader reader2 = new ObjectJsonReader(false);
assertThat(reader2.nextBoolean()).isFalse();
assertThat(reader2.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
JsonReader reader3 = new ObjectJsonReader(null);
assertThat(reader3.nextNull()).isNull();
assertThat(reader3.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
JsonReader reader4 = new ObjectJsonReader(123);
assertThat(reader4.nextLong()).isEqualTo(123);
assertThat(reader4.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
JsonReader reader5 = new ObjectJsonReader(123.4);
assertThat(reader5.nextDouble()).isEqualTo(123.4);
assertThat(reader5.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
JsonReader reader6 = new ObjectJsonReader("Hi");
assertThat(reader6.nextString()).isEqualTo("Hi");
assertThat(reader6.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void list() throws IOException {
JsonReader reader = new ObjectJsonReader(Arrays.asList("Hello", "World"));
reader.beginArray();
assertThat(reader.nextString()).isEqualTo("Hello");
assertThat(reader.nextString()).isEqualTo("World");
reader.endArray();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
@Test public void map() throws IOException {
JsonReader reader = new ObjectJsonReader(singletonMap("Hello", "World"));
reader.beginObject();
assertThat(reader.nextName()).isEqualTo("Hello");
assertThat(reader.nextString()).isEqualTo("World");
reader.endObject();
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_DOCUMENT);
}
}

View file

@ -0,0 +1,9 @@
package com.squareup.moshi;
import org.junit.Test;
public final class ObjectJsonWriterTest {
@Test public void topLevelValueTypes() {
}
}