New magic API to use type adapters for map keys.
This commit is contained in:
parent
a69c32741e
commit
a3520730a0
11 changed files with 418 additions and 29 deletions
|
@ -41,14 +41,14 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||||
if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) {
|
if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) {
|
||||||
if (rawType.getSimpleName().isEmpty()) {
|
if (rawType.getSimpleName().isEmpty()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"cannot serialize anonymous class " + rawType.getName());
|
"Cannot serialize anonymous class " + rawType.getName());
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"cannot serialize non-static nested class " + rawType.getName());
|
"Cannot serialize non-static nested class " + rawType.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Modifier.isAbstract(rawType.getModifiers())) {
|
if (Modifier.isAbstract(rawType.getModifiers())) {
|
||||||
throw new IllegalArgumentException("cannot serialize abstract class " + rawType.getName());
|
throw new IllegalArgumentException("Cannot serialize abstract class " + rawType.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassFactory<Object> classFactory = ClassFactory.get(rawType);
|
ClassFactory<Object> classFactory = ClassFactory.get(rawType);
|
||||||
|
@ -79,7 +79,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||||
// Store it using the field's name. If there was already a field with this name, fail!
|
// Store it using the field's name. If there was already a field with this name, fail!
|
||||||
FieldBinding<?> replaced = fieldBindings.put(field.getName(), fieldBinding);
|
FieldBinding<?> replaced = fieldBindings.put(field.getName(), fieldBinding);
|
||||||
if (replaced != null) {
|
if (replaced != null) {
|
||||||
throw new IllegalArgumentException("field name collision: '" + field.getName() + "'"
|
throw new IllegalArgumentException("Field name collision: '" + field.getName() + "'"
|
||||||
+ " declared by both " + replaced.field.getDeclaringClass().getName()
|
+ " declared by both " + replaced.field.getDeclaringClass().getName()
|
||||||
+ " and superclass " + fieldBinding.field.getDeclaringClass().getName());
|
+ " and superclass " + fieldBinding.field.getDeclaringClass().getName());
|
||||||
}
|
}
|
||||||
|
@ -172,8 +172,7 @@ final class ClassJsonAdapter<T> extends JsonAdapter<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") // We require that field's values are of type T.
|
@SuppressWarnings("unchecked") // We require that field's values are of type T.
|
||||||
private void write(JsonWriter writer, Object value)
|
private void write(JsonWriter writer, Object value) throws IllegalAccessException, IOException {
|
||||||
throws IllegalAccessException, IOException {
|
|
||||||
T fieldValue = (T) field.get(value);
|
T fieldValue = (T) field.get(value);
|
||||||
adapter.toJson(writer, fieldValue);
|
adapter.toJson(writer, fieldValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -919,7 +919,7 @@ public final class JsonReader implements Closeable {
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
// Fall back to parse as a double below.
|
// Fall back to parse as a double below.
|
||||||
}
|
}
|
||||||
} else {
|
} else if (p != PEEKED_BUFFERED) {
|
||||||
throw new JsonDataException("Expected a long but was " + peek()
|
throw new JsonDataException("Expected a long but was " + peek()
|
||||||
+ " at path " + getPath());
|
+ " at path " + getPath());
|
||||||
}
|
}
|
||||||
|
@ -1044,7 +1044,7 @@ public final class JsonReader implements Closeable {
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {
|
||||||
// Fall back to parse as a double below.
|
// Fall back to parse as a double below.
|
||||||
}
|
}
|
||||||
} else {
|
} else if (p != PEEKED_BUFFERED) {
|
||||||
throw new JsonDataException("Expected an int but was " + peek() + " at path " + getPath());
|
throw new JsonDataException("Expected an int but was " + peek() + " at path " + getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1327,6 +1327,17 @@ public final class JsonReader implements Closeable {
|
||||||
throw new IOException(message + " at path " + getPath());
|
throw new IOException(message + " at path " + getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the reader to treat the next name as a string value. This is useful for map adapters so
|
||||||
|
* that arbitrary type adapters can use {@link #nextString} to read a name value.
|
||||||
|
*/
|
||||||
|
void promoteNameToValue() throws IOException {
|
||||||
|
if (hasNext()) {
|
||||||
|
peekedString = nextName();
|
||||||
|
peeked = PEEKED_BUFFERED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A structure, name, or value type in a JSON-encoded string.
|
* A structure, name, or value type in a JSON-encoded string.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -180,6 +180,8 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
|
|
||||||
private boolean serializeNulls;
|
private boolean serializeNulls;
|
||||||
|
|
||||||
|
private boolean promoteNameToValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance that writes a JSON-encoded stream to {@code sink}.
|
* Creates a new instance that writes a JSON-encoded stream to {@code sink}.
|
||||||
*/
|
*/
|
||||||
|
@ -284,6 +286,7 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
public JsonWriter endObject() throws IOException {
|
public JsonWriter endObject() throws IOException {
|
||||||
|
promoteNameToValue = false;
|
||||||
return close(EMPTY_OBJECT, NONEMPTY_OBJECT, "}");
|
return close(EMPTY_OBJECT, NONEMPTY_OBJECT, "}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,6 +370,7 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
deferredName = name;
|
deferredName = name;
|
||||||
pathNames[stackSize - 1] = name;
|
pathNames[stackSize - 1] = name;
|
||||||
|
promoteNameToValue = false;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,6 +392,9 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return nullValue();
|
return nullValue();
|
||||||
}
|
}
|
||||||
|
if (promoteNameToValue) {
|
||||||
|
return name(value);
|
||||||
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue(false);
|
||||||
string(value);
|
string(value);
|
||||||
|
@ -439,6 +446,9 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
if (Double.isNaN(value) || Double.isInfinite(value)) {
|
||||||
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
|
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
|
||||||
}
|
}
|
||||||
|
if (promoteNameToValue) {
|
||||||
|
return name(Double.toString(value));
|
||||||
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue(false);
|
||||||
sink.writeUtf8(Double.toString(value));
|
sink.writeUtf8(Double.toString(value));
|
||||||
|
@ -452,6 +462,9 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
* @return this writer.
|
* @return this writer.
|
||||||
*/
|
*/
|
||||||
public JsonWriter value(long value) throws IOException {
|
public JsonWriter value(long value) throws IOException {
|
||||||
|
if (promoteNameToValue) {
|
||||||
|
return name(Long.toString(value));
|
||||||
|
}
|
||||||
writeDeferredName();
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue(false);
|
||||||
sink.writeUtf8(Long.toString(value));
|
sink.writeUtf8(Long.toString(value));
|
||||||
|
@ -471,12 +484,15 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
return nullValue();
|
return nullValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
writeDeferredName();
|
|
||||||
String string = value.toString();
|
String string = value.toString();
|
||||||
if (!lenient
|
if (!lenient
|
||||||
&& (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
|
&& (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
|
||||||
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
|
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
|
||||||
}
|
}
|
||||||
|
if (promoteNameToValue) {
|
||||||
|
return name(string);
|
||||||
|
}
|
||||||
|
writeDeferredName();
|
||||||
beforeValue(false);
|
beforeValue(false);
|
||||||
sink.writeUtf8(string);
|
sink.writeUtf8(string);
|
||||||
pathIndices[stackSize - 1]++;
|
pathIndices[stackSize - 1]++;
|
||||||
|
@ -612,6 +628,18 @@ public final class JsonWriter implements Closeable, Flushable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the reader to treat the next string value as a name. This is useful for map adapters so
|
||||||
|
* that arbitrary type adapters can use {@link #value(String)} to write a name value.
|
||||||
|
*/
|
||||||
|
void promoteNameToValue() throws IOException {
|
||||||
|
int context = peek();
|
||||||
|
if (context != NONEMPTY_OBJECT && context != EMPTY_OBJECT) {
|
||||||
|
throw new IllegalStateException("Nesting problem.");
|
||||||
|
}
|
||||||
|
promoteNameToValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
|
* Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
|
||||||
* the current location in the JSON value.
|
* the current location in the JSON value.
|
||||||
|
|
|
@ -34,21 +34,26 @@ final class MapJsonAdapter<K, V> extends JsonAdapter<Map<K, V>> {
|
||||||
Class<?> rawType = Types.getRawType(type);
|
Class<?> rawType = Types.getRawType(type);
|
||||||
if (rawType != Map.class) return null;
|
if (rawType != Map.class) return null;
|
||||||
Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType);
|
Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType);
|
||||||
if (keyAndValue[0] != String.class) return null;
|
return new MapJsonAdapter<>(moshi, keyAndValue[0], keyAndValue[1]).nullSafe();
|
||||||
return new MapJsonAdapter<>(moshi, keyAndValue[1]).nullSafe();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private final JsonAdapter<K> keyAdapter;
|
||||||
private final JsonAdapter<V> valueAdapter;
|
private final JsonAdapter<V> valueAdapter;
|
||||||
|
|
||||||
public MapJsonAdapter(Moshi moshi, Type valueType) {
|
public MapJsonAdapter(Moshi moshi, Type keyType, Type valueType) {
|
||||||
|
this.keyAdapter = moshi.adapter(keyType);
|
||||||
this.valueAdapter = moshi.adapter(valueType);
|
this.valueAdapter = moshi.adapter(valueType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void toJson(JsonWriter writer, Map<K, V> map) throws IOException {
|
@Override public void toJson(JsonWriter writer, Map<K, V> map) throws IOException {
|
||||||
writer.beginObject();
|
writer.beginObject();
|
||||||
for (Map.Entry<K, V> entry : map.entrySet()) {
|
for (Map.Entry<K, V> entry : map.entrySet()) {
|
||||||
writer.name((String) entry.getKey());
|
if (entry.getKey() == null) {
|
||||||
|
throw new JsonDataException("Map key is null at path " + writer.getPath());
|
||||||
|
}
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
keyAdapter.toJson(writer, entry.getKey());
|
||||||
valueAdapter.toJson(writer, entry.getValue());
|
valueAdapter.toJson(writer, entry.getValue());
|
||||||
}
|
}
|
||||||
writer.endObject();
|
writer.endObject();
|
||||||
|
@ -58,12 +63,13 @@ final class MapJsonAdapter<K, V> extends JsonAdapter<Map<K, V>> {
|
||||||
LinkedHashTreeMap<K, V> result = new LinkedHashTreeMap<>();
|
LinkedHashTreeMap<K, V> result = new LinkedHashTreeMap<>();
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
@SuppressWarnings("unchecked") // Currently 'K' is always 'String'.
|
reader.promoteNameToValue();
|
||||||
K name = (K) reader.nextName();
|
K name = keyAdapter.fromJson(reader);
|
||||||
V value = valueAdapter.fromJson(reader);
|
V value = valueAdapter.fromJson(reader);
|
||||||
V replaced = result.put(name, value);
|
V replaced = result.put(name, value);
|
||||||
if (replaced != null) {
|
if (replaced != null) {
|
||||||
throw new JsonDataException("object property '" + name + "' has multiple values");
|
throw new JsonDataException("Map key '" + name + "' has multiple values at path "
|
||||||
|
+ reader.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader.endObject();
|
reader.endObject();
|
||||||
|
|
|
@ -90,7 +90,7 @@ public final class Moshi {
|
||||||
deferredAdapters.remove(deferredAdapters.size() - 1);
|
deferredAdapters.remove(deferredAdapters.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("no JsonAdapter for " + type + " annotated " + annotations);
|
throw new IllegalArgumentException("No JsonAdapter for " + type + " annotated " + annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
@ -172,12 +172,12 @@ public final class Moshi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public T fromJson(JsonReader reader) throws IOException {
|
@Override public T fromJson(JsonReader reader) throws IOException {
|
||||||
if (delegate == null) throw new IllegalStateException("type adapter isn't ready");
|
if (delegate == null) throw new IllegalStateException("Type adapter isn't ready");
|
||||||
return delegate.fromJson(reader);
|
return delegate.fromJson(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void toJson(JsonWriter writer, T value) throws IOException {
|
@Override public void toJson(JsonWriter writer, T value) throws IOException {
|
||||||
if (delegate == null) throw new IllegalStateException("type adapter isn't ready");
|
if (delegate == null) throw new IllegalStateException("Type adapter isn't ready");
|
||||||
delegate.toJson(writer, value);
|
delegate.toJson(writer, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,7 @@ public final class ClassJsonAdapterTest {
|
||||||
ClassJsonAdapter.FACTORY.create(ExtendsBaseA.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.FACTORY.create(ExtendsBaseA.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage("field name collision: 'a' declared by both "
|
assertThat(expected).hasMessage("Field name collision: 'a' declared by both "
|
||||||
+ "com.squareup.moshi.ClassJsonAdapterTest$ExtendsBaseA and "
|
+ "com.squareup.moshi.ClassJsonAdapterTest$ExtendsBaseA and "
|
||||||
+ "superclass com.squareup.moshi.ClassJsonAdapterTest$BaseA");
|
+ "superclass com.squareup.moshi.ClassJsonAdapterTest$BaseA");
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,7 @@ public final class ClassJsonAdapterTest {
|
||||||
ClassJsonAdapter.FACTORY.create(NonStatic.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.FACTORY.create(NonStatic.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage("cannot serialize non-static nested class "
|
assertThat(expected).hasMessage("Cannot serialize non-static nested class "
|
||||||
+ "com.squareup.moshi.ClassJsonAdapterTest$NonStatic");
|
+ "com.squareup.moshi.ClassJsonAdapterTest$NonStatic");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,7 +326,7 @@ public final class ClassJsonAdapterTest {
|
||||||
ClassJsonAdapter.FACTORY.create(c.getClass(), NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.FACTORY.create(c.getClass(), NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage("cannot serialize anonymous class " + c.getClass().getName());
|
assertThat(expected).hasMessage("Cannot serialize anonymous class " + c.getClass().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +342,7 @@ public final class ClassJsonAdapterTest {
|
||||||
ClassJsonAdapter.FACTORY.create(Abstract.class, NO_ANNOTATIONS, moshi);
|
ClassJsonAdapter.FACTORY.create(Abstract.class, NO_ANNOTATIONS, moshi);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage("cannot serialize abstract class "
|
assertThat(expected).hasMessage("Cannot serialize abstract class "
|
||||||
+ "com.squareup.moshi.ClassJsonAdapterTest$Abstract");
|
+ "com.squareup.moshi.ClassJsonAdapterTest$Abstract");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,7 +332,7 @@ public final class JsonQualifiersTest {
|
||||||
moshi.adapter(StringAndFooString.class);
|
moshi.adapter(StringAndFooString.class);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage("no JsonAdapter for class java.lang.String "
|
assertThat(expected).hasMessage("No JsonAdapter for class java.lang.String "
|
||||||
+ "annotated [@com.squareup.moshi.JsonQualifiersTest$FooPrefix()]");
|
+ "annotated [@com.squareup.moshi.JsonQualifiersTest$FooPrefix()]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -353,7 +353,7 @@ public final class JsonQualifiersTest {
|
||||||
moshi.adapter(StringAndFooString.class);
|
moshi.adapter(StringAndFooString.class);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage("no JsonAdapter for class java.lang.String "
|
assertThat(expected).hasMessage("No JsonAdapter for class java.lang.String "
|
||||||
+ "annotated [@com.squareup.moshi.JsonQualifiersTest$FooPrefix()]");
|
+ "annotated [@com.squareup.moshi.JsonQualifiersTest$FooPrefix()]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -781,7 +781,7 @@ public final class JsonReaderTest {
|
||||||
reader.close();
|
reader.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void integerMismatchFailuresDoNotAdvance() throws IOException {
|
@Test public void integerMismatchWithDoubleDoesNotAdvance() throws IOException {
|
||||||
JsonReader reader = newReader("[1.5]");
|
JsonReader reader = newReader("[1.5]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
try {
|
try {
|
||||||
|
@ -793,6 +793,30 @@ public final class JsonReaderTest {
|
||||||
reader.endArray();
|
reader.endArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test public void integerMismatchWithLongDoesNotAdvance() throws IOException {
|
||||||
|
JsonReader reader = newReader("[9223372036854775807]");
|
||||||
|
reader.beginArray();
|
||||||
|
try {
|
||||||
|
reader.nextInt();
|
||||||
|
fail();
|
||||||
|
} catch (JsonDataException expected) {
|
||||||
|
}
|
||||||
|
assertThat(reader.nextLong()).isEqualTo(9223372036854775807L);
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void longMismatchWithDoubleDoesNotAdvance() throws IOException {
|
||||||
|
JsonReader reader = newReader("[1.5]");
|
||||||
|
reader.beginArray();
|
||||||
|
try {
|
||||||
|
reader.nextLong();
|
||||||
|
fail();
|
||||||
|
} catch (JsonDataException expected) {
|
||||||
|
}
|
||||||
|
assertThat(reader.nextDouble()).isEqualTo(1.5d);
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
@Test public void stringNullIsNotNull() throws IOException {
|
@Test public void stringNullIsNotNull() throws IOException {
|
||||||
JsonReader reader = newReader("[\"null\"]");
|
JsonReader reader = newReader("[\"null\"]");
|
||||||
reader.beginArray();
|
reader.beginArray();
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import okio.Buffer;
|
import okio.Buffer;
|
||||||
import org.assertj.core.data.MapEntry;
|
import org.assertj.core.data.MapEntry;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static com.squareup.moshi.TestUtil.newReader;
|
import static com.squareup.moshi.TestUtil.newReader;
|
||||||
|
@ -55,7 +56,8 @@ public final class MapJsonAdapterTest {
|
||||||
try {
|
try {
|
||||||
toJson(String.class, Boolean.class, map);
|
toJson(String.class, Boolean.class, map);
|
||||||
fail();
|
fail();
|
||||||
} catch (NullPointerException expected) {
|
} catch (JsonDataException expected) {
|
||||||
|
assertThat(expected).hasMessage("Map key is null at path $.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,10 +106,26 @@ public final class MapJsonAdapterTest {
|
||||||
fromJson(String.class, Integer.class, "{\"c\":1,\"c\":2}");
|
fromJson(String.class, Integer.class, "{\"c\":1,\"c\":2}");
|
||||||
fail();
|
fail();
|
||||||
} catch (JsonDataException expected) {
|
} catch (JsonDataException expected) {
|
||||||
assertThat(expected).hasMessage("object property 'c' has multiple values");
|
assertThat(expected).hasMessage("Map key 'c' has multiple values at path $.c");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** This leans on {@code promoteNameToValue} to do the heavy lifting. */
|
||||||
|
@Test public void mapWithNonStringKeys() throws Exception {
|
||||||
|
Map<Integer, Boolean> map = new LinkedHashMap<>();
|
||||||
|
map.put(5, true);
|
||||||
|
map.put(6, false);
|
||||||
|
map.put(7, null);
|
||||||
|
|
||||||
|
String toJson = toJson(Integer.class, Boolean.class, map);
|
||||||
|
assertThat(toJson).isEqualTo("{\"5\":true,\"6\":false,\"7\":null}");
|
||||||
|
|
||||||
|
Map<String, Boolean> fromJson = fromJson(
|
||||||
|
Integer.class, Boolean.class, "{\"5\":true,\"6\":false,\"7\":null}");
|
||||||
|
assertThat(fromJson).containsExactly(
|
||||||
|
MapEntry.entry(5, true), MapEntry.entry(6, false), MapEntry.entry(7, null));
|
||||||
|
}
|
||||||
|
|
||||||
private <K, V> String toJson(Type keyType, Type valueType, Map<K, V> value) throws IOException {
|
private <K, V> String toJson(Type keyType, Type valueType, Map<K, V> value) throws IOException {
|
||||||
JsonAdapter<Map<K, V>> jsonAdapter = mapAdapter(keyType, valueType);
|
JsonAdapter<Map<K, V>> jsonAdapter = mapAdapter(keyType, valueType);
|
||||||
Buffer buffer = new Buffer();
|
Buffer buffer = new Buffer();
|
||||||
|
|
|
@ -643,7 +643,7 @@ public final class MoshiTest {
|
||||||
Util.jsonAnnotations(uppercaseStringsField));
|
Util.jsonAnnotations(uppercaseStringsField));
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertThat(expected).hasMessage( "no JsonAdapter for java.util.List<java.lang.String> "
|
assertThat(expected).hasMessage("No JsonAdapter for java.util.List<java.lang.String> "
|
||||||
+ "annotated [@com.squareup.moshi.MoshiTest$Uppercase()]");
|
+ "annotated [@com.squareup.moshi.MoshiTest$Uppercase()]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,303 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 okio.Buffer;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
public final class PromoteNameToValueTest {
|
||||||
|
@Test public void readerStringValue() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"a\":1}");
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.a");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
assertThat(reader.nextString()).isEqualTo("a");
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.a");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.a");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerIntegerValue() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"5\":1}");
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(5);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerDoubleValue() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"5.5\":1}");
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5.5");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
assertThat(reader.nextDouble()).isEqualTo(5.5d);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5.5");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5.5");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerBooleanValue() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"true\":1}");
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.true");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
try {
|
||||||
|
reader.nextBoolean();
|
||||||
|
fail();
|
||||||
|
} catch (JsonDataException e) {
|
||||||
|
assertThat(e).hasMessage("Expected a boolean but was STRING at path $.true");
|
||||||
|
}
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.true");
|
||||||
|
assertThat(reader.nextString()).isEqualTo("true");
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.true");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerLongValue() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"5\":1}");
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
assertThat(reader.nextLong()).isEqualTo(5L);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.5");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerNullValue() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"null\":1}");
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.null");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
try {
|
||||||
|
reader.nextNull();
|
||||||
|
fail();
|
||||||
|
} catch (JsonDataException e) {
|
||||||
|
assertThat(e).hasMessage("Expected null but was STRING at path $.null");
|
||||||
|
}
|
||||||
|
assertThat(reader.nextString()).isEqualTo("null");
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.null");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.null");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerMultipleValueObject() throws Exception {
|
||||||
|
JsonReader reader = newReader("{\"a\":1,\"b\":2}");
|
||||||
|
reader.beginObject();
|
||||||
|
assertThat(reader.nextName()).isEqualTo("a");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(1);
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.b");
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.STRING);
|
||||||
|
assertThat(reader.nextString()).isEqualTo("b");
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.b");
|
||||||
|
assertThat(reader.nextInt()).isEqualTo(2);
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.b");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerEmptyValueObject() throws Exception {
|
||||||
|
JsonReader reader = newReader("{}");
|
||||||
|
reader.beginObject();
|
||||||
|
assertThat(reader.peek()).isEqualTo(JsonReader.Token.END_OBJECT);
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$.");
|
||||||
|
reader.endObject();
|
||||||
|
assertThat(reader.getPath()).isEqualTo("$");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void readerUnusedPromotionDoesntPersist() throws Exception {
|
||||||
|
JsonReader reader = new JsonReader(new Buffer().writeUtf8("[{},{\"a\":5}]"));
|
||||||
|
reader.beginArray();
|
||||||
|
reader.beginObject();
|
||||||
|
reader.promoteNameToValue();
|
||||||
|
reader.endObject();
|
||||||
|
reader.beginObject();
|
||||||
|
try {
|
||||||
|
reader.nextString();
|
||||||
|
fail();
|
||||||
|
} catch (JsonDataException expected) {
|
||||||
|
}
|
||||||
|
assertThat(reader.nextName()).isEqualTo("a");
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonReader newReader(String s) {
|
||||||
|
return new JsonReader(new Buffer().writeUtf8(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerStringValue() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
writer.value("a");
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.a");
|
||||||
|
writer.value(1);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.a");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"a\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerIntegerValue() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
writer.value(5);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.5");
|
||||||
|
writer.value(1);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.5");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"5\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerDoubleValue() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
writer.value(5.5d);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.5.5");
|
||||||
|
writer.value(1);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.5.5");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"5.5\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerBooleanValue() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
try {
|
||||||
|
writer.value(true);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertThat(e).hasMessage("Nesting problem.");
|
||||||
|
}
|
||||||
|
writer.value("true");
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.true");
|
||||||
|
writer.value(1);
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"true\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerLongValue() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
writer.value(5L);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.5");
|
||||||
|
writer.value(1);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.5");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"5\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerNullValue() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
try {
|
||||||
|
writer.nullValue();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertThat(e).hasMessage("Nesting problem.");
|
||||||
|
}
|
||||||
|
writer.value("null");
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.null");
|
||||||
|
writer.value(1);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.null");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"null\":1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerMultipleValueObject() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.name("a");
|
||||||
|
writer.value(1);
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
writer.value("b");
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.b");
|
||||||
|
writer.value(2);
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.b");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{\"a\":1,\"b\":2}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerEmptyValueObject() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$.");
|
||||||
|
writer.endObject();
|
||||||
|
assertThat(writer.getPath()).isEqualTo("$");
|
||||||
|
assertThat(buffer.readUtf8()).isEqualTo("{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test public void writerUnusedPromotionDoesntPersist() throws Exception {
|
||||||
|
Buffer buffer = new Buffer();
|
||||||
|
JsonWriter writer = new JsonWriter(buffer);
|
||||||
|
writer.beginArray();
|
||||||
|
writer.beginObject();
|
||||||
|
writer.promoteNameToValue();
|
||||||
|
writer.endObject();
|
||||||
|
writer.beginObject();
|
||||||
|
try {
|
||||||
|
writer.value("a");
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
writer.name("a");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue