diff --git a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java index a6b4361..f2cf8ce 100644 --- a/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/ClassJsonAdapter.java @@ -41,14 +41,14 @@ final class ClassJsonAdapter extends JsonAdapter { if (rawType.getEnclosingClass() != null && !Modifier.isStatic(rawType.getModifiers())) { if (rawType.getSimpleName().isEmpty()) { throw new IllegalArgumentException( - "cannot serialize anonymous class " + rawType.getName()); + "Cannot serialize anonymous class " + rawType.getName()); } else { throw new IllegalArgumentException( - "cannot serialize non-static nested class " + rawType.getName()); + "Cannot serialize non-static nested class " + rawType.getName()); } } if (Modifier.isAbstract(rawType.getModifiers())) { - throw new IllegalArgumentException("cannot serialize abstract class " + rawType.getName()); + throw new IllegalArgumentException("Cannot serialize abstract class " + rawType.getName()); } ClassFactory classFactory = ClassFactory.get(rawType); @@ -79,7 +79,7 @@ final class ClassJsonAdapter extends JsonAdapter { // Store it using the field's name. If there was already a field with this name, fail! FieldBinding replaced = fieldBindings.put(field.getName(), fieldBinding); 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() + " and superclass " + fieldBinding.field.getDeclaringClass().getName()); } @@ -172,8 +172,7 @@ final class ClassJsonAdapter extends JsonAdapter { } @SuppressWarnings("unchecked") // We require that field's values are of type T. - private void write(JsonWriter writer, Object value) - throws IllegalAccessException, IOException { + private void write(JsonWriter writer, Object value) throws IllegalAccessException, IOException { T fieldValue = (T) field.get(value); adapter.toJson(writer, fieldValue); } diff --git a/moshi/src/main/java/com/squareup/moshi/JsonReader.java b/moshi/src/main/java/com/squareup/moshi/JsonReader.java index defb540..814f1d1 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonReader.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonReader.java @@ -919,7 +919,7 @@ public final class JsonReader implements Closeable { } catch (NumberFormatException ignored) { // Fall back to parse as a double below. } - } else { + } else if (p != PEEKED_BUFFERED) { throw new JsonDataException("Expected a long but was " + peek() + " at path " + getPath()); } @@ -1044,7 +1044,7 @@ public final class JsonReader implements Closeable { } catch (NumberFormatException ignored) { // 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()); } @@ -1327,6 +1327,17 @@ public final class JsonReader implements Closeable { 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. */ diff --git a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java index 78b44ed..40566d8 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java @@ -180,6 +180,8 @@ public final class JsonWriter implements Closeable, Flushable { private boolean serializeNulls; + private boolean promoteNameToValue; + /** * 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. */ public JsonWriter endObject() throws IOException { + promoteNameToValue = false; return close(EMPTY_OBJECT, NONEMPTY_OBJECT, "}"); } @@ -367,6 +370,7 @@ public final class JsonWriter implements Closeable, Flushable { } deferredName = name; pathNames[stackSize - 1] = name; + promoteNameToValue = false; return this; } @@ -388,6 +392,9 @@ public final class JsonWriter implements Closeable, Flushable { if (value == null) { return nullValue(); } + if (promoteNameToValue) { + return name(value); + } writeDeferredName(); beforeValue(false); string(value); @@ -439,6 +446,9 @@ public final class JsonWriter implements Closeable, Flushable { if (Double.isNaN(value) || Double.isInfinite(value)) { throw new IllegalArgumentException("Numeric values must be finite, but was " + value); } + if (promoteNameToValue) { + return name(Double.toString(value)); + } writeDeferredName(); beforeValue(false); sink.writeUtf8(Double.toString(value)); @@ -452,6 +462,9 @@ public final class JsonWriter implements Closeable, Flushable { * @return this writer. */ public JsonWriter value(long value) throws IOException { + if (promoteNameToValue) { + return name(Long.toString(value)); + } writeDeferredName(); beforeValue(false); sink.writeUtf8(Long.toString(value)); @@ -471,12 +484,15 @@ public final class JsonWriter implements Closeable, Flushable { return nullValue(); } - writeDeferredName(); String string = value.toString(); if (!lenient && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { throw new IllegalArgumentException("Numeric values must be finite, but was " + value); } + if (promoteNameToValue) { + return name(string); + } + writeDeferredName(); beforeValue(false); sink.writeUtf8(string); 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 JsonPath to * the current location in the JSON value. diff --git a/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java index 289d034..c4f15db 100644 --- a/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java @@ -34,21 +34,26 @@ final class MapJsonAdapter extends JsonAdapter> { Class rawType = Types.getRawType(type); if (rawType != Map.class) return null; Type[] keyAndValue = Types.mapKeyAndValueTypes(type, rawType); - if (keyAndValue[0] != String.class) return null; - return new MapJsonAdapter<>(moshi, keyAndValue[1]).nullSafe(); + return new MapJsonAdapter<>(moshi, keyAndValue[0], keyAndValue[1]).nullSafe(); } }; + private final JsonAdapter keyAdapter; private final JsonAdapter 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); } @Override public void toJson(JsonWriter writer, Map map) throws IOException { writer.beginObject(); for (Map.Entry 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()); } writer.endObject(); @@ -58,12 +63,13 @@ final class MapJsonAdapter extends JsonAdapter> { LinkedHashTreeMap result = new LinkedHashTreeMap<>(); reader.beginObject(); while (reader.hasNext()) { - @SuppressWarnings("unchecked") // Currently 'K' is always 'String'. - K name = (K) reader.nextName(); + reader.promoteNameToValue(); + K name = keyAdapter.fromJson(reader); V value = valueAdapter.fromJson(reader); V replaced = result.put(name, value); 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(); diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.java b/moshi/src/main/java/com/squareup/moshi/Moshi.java index 5cf9428..0075175 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.java +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.java @@ -90,7 +90,7 @@ public final class Moshi { 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 { @@ -172,12 +172,12 @@ public final class Moshi { } @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); } @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); } } diff --git a/moshi/src/test/java/com/squareup/moshi/ClassJsonAdapterTest.java b/moshi/src/test/java/com/squareup/moshi/ClassJsonAdapterTest.java index aa02dbc..5c058ca 100644 --- a/moshi/src/test/java/com/squareup/moshi/ClassJsonAdapterTest.java +++ b/moshi/src/test/java/com/squareup/moshi/ClassJsonAdapterTest.java @@ -160,7 +160,7 @@ public final class ClassJsonAdapterTest { ClassJsonAdapter.FACTORY.create(ExtendsBaseA.class, NO_ANNOTATIONS, moshi); fail(); } 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 " + "superclass com.squareup.moshi.ClassJsonAdapterTest$BaseA"); } @@ -306,7 +306,7 @@ public final class ClassJsonAdapterTest { ClassJsonAdapter.FACTORY.create(NonStatic.class, NO_ANNOTATIONS, moshi); fail(); } 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"); } } @@ -326,7 +326,7 @@ public final class ClassJsonAdapterTest { ClassJsonAdapter.FACTORY.create(c.getClass(), NO_ANNOTATIONS, moshi); fail(); } 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); fail(); } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessage("cannot serialize abstract class " + assertThat(expected).hasMessage("Cannot serialize abstract class " + "com.squareup.moshi.ClassJsonAdapterTest$Abstract"); } } diff --git a/moshi/src/test/java/com/squareup/moshi/JsonQualifiersTest.java b/moshi/src/test/java/com/squareup/moshi/JsonQualifiersTest.java index f1a252a..416afa2 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonQualifiersTest.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonQualifiersTest.java @@ -332,7 +332,7 @@ public final class JsonQualifiersTest { moshi.adapter(StringAndFooString.class); fail(); } 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()]"); } } @@ -353,7 +353,7 @@ public final class JsonQualifiersTest { moshi.adapter(StringAndFooString.class); fail(); } 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()]"); } } diff --git a/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java b/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java index aec035a..af54873 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonReaderTest.java @@ -781,7 +781,7 @@ public final class JsonReaderTest { reader.close(); } - @Test public void integerMismatchFailuresDoNotAdvance() throws IOException { + @Test public void integerMismatchWithDoubleDoesNotAdvance() throws IOException { JsonReader reader = newReader("[1.5]"); reader.beginArray(); try { @@ -793,6 +793,30 @@ public final class JsonReaderTest { 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 { JsonReader reader = newReader("[\"null\"]"); reader.beginArray(); diff --git a/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java b/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java index f5860ee..96cdcc0 100644 --- a/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java +++ b/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java @@ -23,6 +23,7 @@ import java.util.LinkedHashMap; import java.util.Map; import okio.Buffer; import org.assertj.core.data.MapEntry; +import org.junit.Ignore; import org.junit.Test; import static com.squareup.moshi.TestUtil.newReader; @@ -55,7 +56,8 @@ public final class MapJsonAdapterTest { try { toJson(String.class, Boolean.class, map); 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}"); fail(); } 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 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 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 String toJson(Type keyType, Type valueType, Map value) throws IOException { JsonAdapter> jsonAdapter = mapAdapter(keyType, valueType); Buffer buffer = new Buffer(); diff --git a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java index 433965f..6ca6646 100644 --- a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java +++ b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java @@ -643,7 +643,7 @@ public final class MoshiTest { Util.jsonAnnotations(uppercaseStringsField)); fail(); } catch (IllegalArgumentException expected) { - assertThat(expected).hasMessage( "no JsonAdapter for java.util.List " + assertThat(expected).hasMessage("No JsonAdapter for java.util.List " + "annotated [@com.squareup.moshi.MoshiTest$Uppercase()]"); } } diff --git a/moshi/src/test/java/com/squareup/moshi/PromoteNameToValueTest.java b/moshi/src/test/java/com/squareup/moshi/PromoteNameToValueTest.java new file mode 100644 index 0000000..235097a --- /dev/null +++ b/moshi/src/test/java/com/squareup/moshi/PromoteNameToValueTest.java @@ -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"); + } +}