Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
b37e7c2dc4 |
6 changed files with 1245 additions and 14 deletions
|
@ -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.
|
||||
|
|
551
moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java
Normal file
551
moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
263
moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java
Normal file
263
moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
|
389
moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java
Normal file
389
moshi/src/test/java/com/squareup/moshi/ObjectJsonReaderTest.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.squareup.moshi;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public final class ObjectJsonWriterTest {
|
||||
@Test public void topLevelValueTypes() {
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue