From 5ca491fe5055574d36fe0d3cdc4607ec375c1547 Mon Sep 17 00:00:00 2001 From: jwilson Date: Mon, 23 Jan 2017 20:46:35 -0500 Subject: [PATCH] Implement ObjectJsonWriter. https://github.com/square/moshi/issues/89 --- .../moshi/BufferedSinkJsonWriter.java | 4 +- .../com/squareup/moshi/MapJsonAdapter.java | 2 +- .../com/squareup/moshi/ObjectJsonWriter.java | 237 ++++++++ .../squareup/moshi/StandardJsonAdapters.java | 8 +- .../moshi/BufferedSinkJsonWriterTest.java | 566 +----------------- .../com/squareup/moshi/JsonReaderFactory.java | 8 +- .../com/squareup/moshi/JsonWriterFactory.java | 52 +- .../com/squareup/moshi/JsonWriterTest.java | 565 +++++++++++++++++ .../squareup/moshi/MapJsonAdapterTest.java | 2 +- .../squareup/moshi/ObjectJsonWriterTest.java | 70 +++ 10 files changed, 953 insertions(+), 561 deletions(-) create mode 100644 moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java create mode 100644 moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java create mode 100644 moshi/src/test/java/com/squareup/moshi/ObjectJsonWriterTest.java diff --git a/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java b/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java index 2927a19..6fa32f3 100644 --- a/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java @@ -188,7 +188,7 @@ final class BufferedSinkJsonWriter extends JsonWriter { } /** - * Returns the value on the top of the stack. + * Returns the scope on the top of the stack. */ private int peek() { if (stackSize == 0) { @@ -212,7 +212,7 @@ final class BufferedSinkJsonWriter extends JsonWriter { throw new IllegalStateException("JsonWriter is closed."); } if (deferredName != null) { - throw new IllegalStateException(); + throw new IllegalStateException("Nesting problem."); } deferredName = name; pathNames[stackSize - 1] = name; diff --git a/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java index 889d1f6..3158108 100644 --- a/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java +++ b/moshi/src/main/java/com/squareup/moshi/MapJsonAdapter.java @@ -69,7 +69,7 @@ final class MapJsonAdapter extends JsonAdapter> { V replaced = result.put(name, value); if (replaced != null) { throw new JsonDataException("Map key '" + name + "' has multiple values at path " - + reader.getPath()); + + reader.getPath() + ": " + replaced + " and " + value); } } reader.endObject(); diff --git a/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java b/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java new file mode 100644 index 0000000..15a1174 --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2017 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.moshi; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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_DOCUMENT; + +/** Writes JSON by building a Java object comprising maps, lists, and JSON primitives. */ +final class ObjectJsonWriter extends JsonWriter { + private String indent; + private boolean lenient; + private boolean serializeNulls; + + private final Object[] stack = new Object[32]; + private final int[] scopes = new int[32]; + private final String[] pathNames = new String[32]; + private final int[] pathIndices = new int[32]; + private int stackSize = 0; + private String deferredName; + + ObjectJsonWriter() { + scopes[stackSize++] = EMPTY_DOCUMENT; + } + + public Object root() { + int size = stackSize; + if (size > 1 || size == 1 && scopes[size - 1] != NONEMPTY_DOCUMENT) { + throw new IllegalStateException("Incomplete document"); + } + return stack[0]; + } + + @Override public void setIndent(String indent) { + this.indent = indent; + } + + @Override public String getIndent() { + return indent; + } + + @Override public void setLenient(boolean lenient) { + this.lenient = lenient; + } + + @Override public boolean isLenient() { + return lenient; + } + + @Override public void setSerializeNulls(boolean serializeNulls) { + this.serializeNulls = serializeNulls; + } + + @Override public boolean getSerializeNulls() { + return serializeNulls; + } + + @Override public JsonWriter beginArray() throws IOException { + if (stackSize == stack.length) { + throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?"); + } + List list = new ArrayList<>(); + add(list); + stack[stackSize] = list; + scopes[stackSize] = EMPTY_ARRAY; + pathIndices[stackSize] = 0; + stackSize++; + return this; + } + + @Override public JsonWriter endArray() throws IOException { + if (peek() != EMPTY_ARRAY) { + throw new IllegalStateException("Nesting problem."); + } + stackSize--; + stack[stackSize] = null; + pathIndices[stackSize - 1]++; + return this; + } + + @Override public JsonWriter beginObject() throws IOException { + if (stackSize == stack.length) { + throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?"); + } + Map map = new LinkedHashTreeMap<>(); + add(map); + stack[stackSize] = map; + scopes[stackSize] = EMPTY_OBJECT; + stackSize++; + return this; + } + + @Override public JsonWriter endObject() throws IOException { + if (peek() != EMPTY_OBJECT || deferredName != null) { + throw new IllegalStateException("Nesting problem."); + } + stackSize--; + stack[stackSize] = null; + pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected! + pathIndices[stackSize - 1]++; + return this; + } + + @Override public JsonWriter name(String name) throws IOException { + if (name == null) { + throw new NullPointerException("name == null"); + } + if (stackSize == 0) { + throw new IllegalStateException("JsonWriter is closed."); + } + if (peek() != EMPTY_OBJECT || deferredName != null) { + throw new IllegalStateException("Nesting problem."); + } + pathNames[stackSize - 1] = name; + deferredName = name; + return this; + } + + @Override public JsonWriter value(String value) throws IOException { + return add(value); + } + + @Override public JsonWriter nullValue() throws IOException { + return add(null); + } + + @Override public JsonWriter value(boolean value) throws IOException { + return add(value); + } + + @Override public JsonWriter value(Boolean value) throws IOException { + return add(value); + } + + @Override public JsonWriter value(double value) throws IOException { + return value(Double.valueOf(value)); + } + + @Override public JsonWriter value(long value) throws IOException { + return add(value); + } + + @Override public JsonWriter value(Number value) throws IOException { + if (!lenient) { + double d = value.doubleValue(); + if (d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY || Double.isNaN(d)) { + throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + } + } + return add(value); + } + + @Override void promoteNameToValue() throws IOException { + throw new UnsupportedOperationException(); + } + + @Override public String getPath() { + return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices); + } + + @Override public void close() throws IOException { + int size = stackSize; + if (size > 1 || size == 1 && scopes[size - 1] != NONEMPTY_DOCUMENT) { + throw new IOException("Incomplete document"); + } + stackSize = 0; + } + + @Override public void flush() throws IOException { + if (stackSize == 0) { + throw new IllegalStateException("JsonWriter is closed."); + } + } + + /** + * Returns the scope on the top of the stack. + */ + private int peek() { + if (stackSize == 0) { + throw new IllegalStateException("JsonWriter is closed."); + } + return scopes[stackSize - 1]; + } + + private ObjectJsonWriter add(Object newTop) { + int scope = peek(); + + if (stackSize == 1) { + if (scope != EMPTY_DOCUMENT) { + throw new IllegalStateException("JSON must have only one top-level value."); + } + scopes[stackSize - 1] = NONEMPTY_DOCUMENT; + stack[stackSize - 1] = newTop; + + } else if (scope == EMPTY_OBJECT && deferredName != null) { + if (newTop != null || serializeNulls) { + @SuppressWarnings("unchecked") // Our maps always have string keys and object values. + Map map = (Map) stack[stackSize - 1]; + Object replaced = map.put(deferredName, newTop); + if (replaced != null) { + throw new IllegalArgumentException("Map key '" + deferredName + + "' has multiple values at path " + getPath() + ": " + replaced + " and " + newTop); + } + } + deferredName = null; + + } else if (scope == EMPTY_ARRAY) { + @SuppressWarnings("unchecked") // Our lists always have object values. + List list = (List) stack[stackSize - 1]; + list.add(newTop); + + } else { + throw new IllegalStateException("Nesting problem."); + } + + return this; + } +} diff --git a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java index fb7960c..034c5d6 100644 --- a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java +++ b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java @@ -286,7 +286,13 @@ final class StandardJsonAdapters { Map map = new LinkedHashTreeMap<>(); reader.beginObject(); while (reader.hasNext()) { - map.put(reader.nextName(), fromJson(reader)); + String name = reader.nextName(); + Object value = fromJson(reader); + Object replaced = map.put(name, value); + if (replaced != null) { + throw new JsonDataException("Map key '" + name + "' has multiple values at path " + + reader.getPath() + ": " + replaced + " and " + value); + } } reader.endObject(); return map; diff --git a/moshi/src/test/java/com/squareup/moshi/BufferedSinkJsonWriterTest.java b/moshi/src/test/java/com/squareup/moshi/BufferedSinkJsonWriterTest.java index 66d1af2..c563afe 100644 --- a/moshi/src/test/java/com/squareup/moshi/BufferedSinkJsonWriterTest.java +++ b/moshi/src/test/java/com/squareup/moshi/BufferedSinkJsonWriterTest.java @@ -16,468 +16,15 @@ package com.squareup.moshi; import java.io.IOException; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.List; +import okio.Buffer; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -@RunWith(Parameterized.class) public final class BufferedSinkJsonWriterTest { - @Parameter public JsonWriterFactory factory; - - @Parameters(name = "{0}") - public static List parameters() { - return JsonWriterFactory.factories(); - } - - @Test public void nullsValuesNotSerializedByDefault() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - writer.name("a"); - writer.nullValue(); - writer.endObject(); - writer.close(); - assertThat(factory.json()).isEqualTo("{}"); - } - - @Test public void nullsValuesSerializedWhenConfigured() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.setSerializeNulls(true); - writer.beginObject(); - writer.name("a"); - writer.nullValue(); - writer.endObject(); - writer.close(); - assertThat(factory.json()).isEqualTo("{\"a\":null}"); - } - - @Test public void topLevelBoolean() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.value(true); - writer.close(); - assertThat(factory.json()).isEqualTo("true"); - } - - @Test public void topLevelNull() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.nullValue(); - writer.close(); - assertThat(factory.json()).isEqualTo("null"); - } - - @Test public void topLevelInt() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.value(123); - writer.close(); - assertThat(factory.json()).isEqualTo("123"); - } - - @Test public void topLevelDouble() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.value(123.4); - writer.close(); - assertThat(factory.json()).isEqualTo("123.4"); - } - - @Test public void topLevelString() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.value("a"); - writer.close(); - assertThat(factory.json()).isEqualTo("\"a\""); - } - - @Test public void invalidTopLevelTypes() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.name("hello"); - try { - writer.value("world"); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void twoNames() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - writer.name("a"); - try { - writer.name("a"); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void nameWithoutValue() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - writer.name("a"); - try { - writer.endObject(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void valueWithoutName() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - try { - writer.value(true); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void multipleTopLevelValues() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray().endArray(); - try { - writer.beginArray(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void badNestingObject() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.beginObject(); - try { - writer.endArray(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void badNestingArray() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.beginArray(); - try { - writer.endObject(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void nullName() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - try { - writer.name(null); - fail(); - } catch (NullPointerException expected) { - } - } - - @Test public void nullStringValue() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.setSerializeNulls(true); - writer.beginObject(); - writer.name("a"); - writer.value((String) null); - writer.endObject(); - assertThat(factory.json()).isEqualTo("{\"a\":null}"); - } - - @Test public void nonFiniteDoubles() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - try { - writer.value(Double.NaN); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - writer.value(Double.NEGATIVE_INFINITY); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - writer.value(Double.POSITIVE_INFINITY); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - @Test public void nonFiniteBoxedDoubles() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - try { - writer.value(new Double(Double.NaN)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - writer.value(new Double(Double.NEGATIVE_INFINITY)); - fail(); - } catch (IllegalArgumentException expected) { - } - try { - writer.value(new Double(Double.POSITIVE_INFINITY)); - fail(); - } catch (IllegalArgumentException expected) { - } - } - - @Test public void doubles() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value(-0.0); - writer.value(1.0); - writer.value(Double.MAX_VALUE); - writer.value(Double.MIN_VALUE); - writer.value(0.0); - writer.value(-0.5); - writer.value(2.2250738585072014E-308); - writer.value(Math.PI); - writer.value(Math.E); - writer.endArray(); - writer.close(); - assertThat(factory.json()).isEqualTo("[-0.0," - + "1.0," - + "1.7976931348623157E308," - + "4.9E-324," - + "0.0," - + "-0.5," - + "2.2250738585072014E-308," - + "3.141592653589793," - + "2.718281828459045]"); - } - - @Test public void longs() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value(0); - writer.value(1); - writer.value(-1); - writer.value(Long.MIN_VALUE); - writer.value(Long.MAX_VALUE); - writer.endArray(); - writer.close(); - assertThat(factory.json()).isEqualTo("[0," - + "1," - + "-1," - + "-9223372036854775808," - + "9223372036854775807]"); - } - - @Test public void numbers() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value(new BigInteger("0")); - writer.value(new BigInteger("9223372036854775808")); - writer.value(new BigInteger("-9223372036854775809")); - writer.value(new BigDecimal("3.141592653589793238462643383")); - writer.endArray(); - writer.close(); - assertThat(factory.json()).isEqualTo("[0," - + "9223372036854775808," - + "-9223372036854775809," - + "3.141592653589793238462643383]"); - } - - @Test public void booleans() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value(true); - writer.value(false); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[true,false]"); - } - - @Test public void boxedBooleans() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value((Boolean) true); - writer.value((Boolean) false); - writer.value((Boolean) null); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[true,false,null]"); - } - - @Test public void nulls() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.nullValue(); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[null]"); - } - - @Test public void strings() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value("a"); - writer.value("a\""); - writer.value("\""); - writer.value(":"); - writer.value(","); - writer.value("\b"); - writer.value("\f"); - writer.value("\n"); - writer.value("\r"); - writer.value("\t"); - writer.value(" "); - writer.value("\\"); - writer.value("{"); - writer.value("}"); - writer.value("["); - writer.value("]"); - writer.value("\0"); - writer.value("\u0019"); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[\"a\"," - + "\"a\\\"\"," - + "\"\\\"\"," - + "\":\"," - + "\",\"," - + "\"\\b\"," - + "\"\\f\"," - + "\"\\n\"," - + "\"\\r\"," - + "\"\\t\"," - + "\" \"," - + "\"\\\\\"," - + "\"{\"," - + "\"}\"," - + "\"[\"," - + "\"]\"," - + "\"\\u0000\"," - + "\"\\u0019\"]"); - } - - @Test public void unicodeLineBreaksEscaped() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.value("\u2028 \u2029"); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[\"\\u2028 \\u2029\"]"); - } - - @Test public void emptyArray() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[]"); - } - - @Test public void emptyObject() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - writer.endObject(); - assertThat(factory.json()).isEqualTo("{}"); - } - - @Test public void objectsInArrays() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.beginObject(); - writer.name("a").value(5); - writer.name("b").value(false); - writer.endObject(); - writer.beginObject(); - writer.name("c").value(6); - writer.name("d").value(true); - writer.endObject(); - writer.endArray(); - assertThat(factory.json()).isEqualTo("[{\"a\":5,\"b\":false}," - + "{\"c\":6,\"d\":true}]"); - } - - @Test public void arraysInObjects() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - writer.name("a"); - writer.beginArray(); - writer.value(5); - writer.value(false); - writer.endArray(); - writer.name("b"); - writer.beginArray(); - writer.value(6); - writer.value(true); - writer.endArray(); - writer.endObject(); - assertThat(factory.json()).isEqualTo("{\"a\":[5,false]," - + "\"b\":[6,true]}"); - } - - @Test public void deepNestingArrays() throws IOException { - JsonWriter writer = factory.newWriter(); - for (int i = 0; i < 31; i++) { - writer.beginArray(); - } - for (int i = 0; i < 31; i++) { - writer.endArray(); - } - assertThat(factory.json()) - .isEqualTo("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); - } - - @Test public void tooDeepNestingArrays() throws IOException { - JsonWriter writer = factory.newWriter(); - for (int i = 0; i < 31; i++) { - writer.beginArray(); - } - try { - writer.beginArray(); - fail(); - } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Nesting too deep at $[0][0][0][0][0][0][0][0][0][0][0][0][0]" - + "[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]: circular reference?"); - } - } - - @Test public void deepNestingObjects() throws IOException { - JsonWriter writer = factory.newWriter(); - for (int i = 0; i < 31; i++) { - writer.beginObject(); - writer.name("a"); - } - writer.value(true); - for (int i = 0; i < 31; i++) { - writer.endObject(); - } - assertThat(factory.json()).isEqualTo("" - + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" - + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" - + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":true}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"); - } - - @Test public void tooDeepNestingObjects() throws IOException { - JsonWriter writer = factory.newWriter(); - for (int i = 0; i < 31; i++) { - writer.beginObject(); - writer.name("a"); - } - try { - writer.beginObject(); - fail(); - } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Nesting too deep at $.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a." - + "a.a.a.a.a.a.a.a.a.a.a.a: circular reference?"); - } - } - - @Test public void repeatedName() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginObject(); - writer.name("a").value(true); - writer.name("a").value(false); - writer.endObject(); - // JsonWriter doesn't attempt to detect duplicate names - assertThat(factory.json()).isEqualTo("{\"a\":true,\"a\":false}"); - } - @Test public void prettyPrintObject() throws IOException { - JsonWriter writer = factory.newWriter(); + Buffer buffer = new Buffer(); + JsonWriter writer = JsonWriter.of(buffer); writer.setSerializeNulls(true); writer.setIndent(" "); @@ -510,11 +57,12 @@ public final class BufferedSinkJsonWriterTest { + " \"i\": 9.0\n" + " }\n" + "}"; - assertThat(factory.json()).isEqualTo(expected); + assertThat(buffer.readUtf8()).isEqualTo(expected); } @Test public void prettyPrintArray() throws IOException { - JsonWriter writer = factory.newWriter(); + Buffer buffer = new Buffer(); + JsonWriter writer = JsonWriter.of(buffer); writer.setIndent(" "); writer.beginArray(); @@ -546,99 +94,17 @@ public final class BufferedSinkJsonWriterTest { + " 9.0\n" + " ]\n" + "]"; - assertThat(factory.json()).isEqualTo(expected); + assertThat(buffer.readUtf8()).isEqualTo(expected); } - @Test public void lenientWriterPermitsMultipleTopLevelValues() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.setLenient(true); - writer.beginArray(); - writer.endArray(); - writer.beginArray(); - writer.endArray(); - writer.close(); - assertThat(factory.json()).isEqualTo("[][]"); - } - - @Test public void strictWriterDoesNotPermitMultipleTopLevelValues() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - try { - writer.beginArray(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void closedWriterThrowsOnStructure() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - writer.close(); - try { - writer.beginArray(); - fail(); - } catch (IllegalStateException expected) { - } - try { - writer.endArray(); - fail(); - } catch (IllegalStateException expected) { - } - try { - writer.beginObject(); - fail(); - } catch (IllegalStateException expected) { - } - try { - writer.endObject(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void closedWriterThrowsOnName() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - writer.close(); - try { - writer.name("a"); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void closedWriterThrowsOnValue() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - writer.close(); - try { - writer.value("a"); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void closedWriterThrowsOnFlush() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - writer.close(); - try { - writer.flush(); - fail(); - } catch (IllegalStateException expected) { - } - } - - @Test public void writerCloseIsIdempotent() throws IOException { - JsonWriter writer = factory.newWriter(); - writer.beginArray(); - writer.endArray(); - writer.close(); - writer.close(); + @Test public void repeatedNameIgnored() throws IOException { + Buffer buffer = new Buffer(); + JsonWriter writer = JsonWriter.of(buffer); + writer.beginObject(); + writer.name("a").value(1); + writer.name("a").value(2); + writer.endObject(); + // JsonWriter doesn't attempt to detect duplicate names + assertThat(buffer.readUtf8()).isEqualTo("{\"a\":1,\"a\":2}"); } } diff --git a/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java b/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java index eed23cb..d8ca856 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonReaderFactory.java @@ -28,7 +28,7 @@ abstract class JsonReaderFactory { return JsonReader.of(buffer); } - @Override public boolean supportsMultipleTopLevelValuesInOneDocument() { + @Override boolean supportsMultipleTopLevelValuesInOneDocument() { return true; } @@ -45,7 +45,7 @@ abstract class JsonReaderFactory { } // TODO(jwilson): fix precision checks and delete his method. - @Override public boolean implementsStrictPrecision() { + @Override boolean implementsStrictPrecision() { return false; } @@ -61,11 +61,11 @@ abstract class JsonReaderFactory { abstract JsonReader newReader(String json) throws IOException; - public boolean supportsMultipleTopLevelValuesInOneDocument() { + boolean supportsMultipleTopLevelValuesInOneDocument() { return false; } - public boolean implementsStrictPrecision() { + boolean implementsStrictPrecision() { return true; } } diff --git a/moshi/src/test/java/com/squareup/moshi/JsonWriterFactory.java b/moshi/src/test/java/com/squareup/moshi/JsonWriterFactory.java index 0defac7..820f8b7 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonWriterFactory.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonWriterFactory.java @@ -15,11 +15,15 @@ */ package com.squareup.moshi; +import java.io.IOException; import java.util.Arrays; import java.util.List; import okio.Buffer; abstract class JsonWriterFactory { + private static final Moshi MOSHI = new Moshi.Builder().build(); + private static final JsonAdapter OBJECT_ADAPTER = MOSHI.adapter(Object.class); + static List factories() { final JsonWriterFactory bufferedSink = new JsonWriterFactory() { Buffer buffer; @@ -35,15 +39,59 @@ abstract class JsonWriterFactory { return result; } + @Override boolean supportsMultipleTopLevelValuesInOneDocument() { + return true; + } + @Override public String toString() { return "BufferedSinkJsonWriter"; } }; - return Arrays.asList( - new Object[] { bufferedSink }); + final JsonWriterFactory object = new JsonWriterFactory() { + ObjectJsonWriter writer; + + @Override JsonWriter newWriter() { + writer = new ObjectJsonWriter(); + return writer; + } + + @Override String json() { + // This writer writes a DOM. Use other Moshi features to serialize it as a string. + try { + Buffer buffer = new Buffer(); + JsonWriter bufferedSinkWriter = JsonWriter.of(buffer); + bufferedSinkWriter.setSerializeNulls(true); + OBJECT_ADAPTER.toJson(bufferedSinkWriter, writer.root()); + return buffer.readUtf8(); + } catch (IOException e) { + throw new AssertionError(); + } + } + + // TODO(jwilson): support BigDecimal and BigInteger and delete his method. + @Override boolean supportsBigNumbers() { + return false; + } + + @Override public String toString() { + return "ObjectJsonWriter"; + } + }; + + return Arrays.asList( + new Object[] { bufferedSink }, + new Object[] { object }); } abstract JsonWriter newWriter(); abstract String json(); + + boolean supportsMultipleTopLevelValuesInOneDocument() { + return false; + } + + boolean supportsBigNumbers() { + return true; + } } diff --git a/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java b/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java new file mode 100644 index 0000000..55abfa6 --- /dev/null +++ b/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2010 Google 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.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +@RunWith(Parameterized.class) +public final class JsonWriterTest { + @Parameter public JsonWriterFactory factory; + + @Parameters(name = "{0}") + public static List parameters() { + return JsonWriterFactory.factories(); + } + + @Test public void nullsValuesNotSerializedByDefault() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + writer.name("a"); + writer.nullValue(); + writer.endObject(); + writer.close(); + assertThat(factory.json()).isEqualTo("{}"); + } + + @Test public void nullsValuesSerializedWhenConfigured() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.setSerializeNulls(true); + writer.beginObject(); + writer.name("a"); + writer.nullValue(); + writer.endObject(); + writer.close(); + assertThat(factory.json()).isEqualTo("{\"a\":null}"); + } + + @Test public void topLevelBoolean() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.value(true); + writer.close(); + assertThat(factory.json()).isEqualTo("true"); + } + + @Test public void topLevelNull() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.nullValue(); + writer.close(); + assertThat(factory.json()).isEqualTo("null"); + } + + @Test public void topLevelInt() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.value(123); + writer.close(); + assertThat(factory.json()).isEqualTo("123"); + } + + @Test public void topLevelDouble() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.value(123.4); + writer.close(); + assertThat(factory.json()).isEqualTo("123.4"); + } + + @Test public void topLevelString() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.value("a"); + writer.close(); + assertThat(factory.json()).isEqualTo("\"a\""); + } + + @Test public void invalidTopLevelTypes() throws IOException { + JsonWriter writer = factory.newWriter(); + try { + writer.name("hello").value("world"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void twoNames() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + writer.name("a"); + try { + writer.name("a"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void nameWithoutValue() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + writer.name("a"); + try { + writer.endObject(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void valueWithoutName() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + try { + writer.value(true); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void multipleTopLevelValues() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray().endArray(); + try { + writer.beginArray(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void badNestingObject() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.beginObject(); + try { + writer.endArray(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void badNestingArray() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.beginArray(); + try { + writer.endObject(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void nullName() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + try { + writer.name(null); + fail(); + } catch (NullPointerException expected) { + } + } + + @Test public void nullStringValue() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.setSerializeNulls(true); + writer.beginObject(); + writer.name("a"); + writer.value((String) null); + writer.endObject(); + assertThat(factory.json()).isEqualTo("{\"a\":null}"); + } + + @Test public void nonFiniteDoubles() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + try { + writer.value(Double.NaN); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + writer.value(Double.NEGATIVE_INFINITY); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + writer.value(Double.POSITIVE_INFINITY); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void nonFiniteBoxedDoubles() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + try { + writer.value(new Double(Double.NaN)); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + writer.value(new Double(Double.NEGATIVE_INFINITY)); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + writer.value(new Double(Double.POSITIVE_INFINITY)); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void doubles() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value(-0.0); + writer.value(1.0); + writer.value(Double.MAX_VALUE); + writer.value(Double.MIN_VALUE); + writer.value(0.0); + writer.value(-0.5); + writer.value(2.2250738585072014E-308); + writer.value(Math.PI); + writer.value(Math.E); + writer.endArray(); + writer.close(); + assertThat(factory.json()).isEqualTo("[-0.0," + + "1.0," + + "1.7976931348623157E308," + + "4.9E-324," + + "0.0," + + "-0.5," + + "2.2250738585072014E-308," + + "3.141592653589793," + + "2.718281828459045]"); + } + + @Test public void longs() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value(0); + writer.value(1); + writer.value(-1); + writer.value(Long.MIN_VALUE); + writer.value(Long.MAX_VALUE); + writer.endArray(); + writer.close(); + assertThat(factory.json()).isEqualTo("[0," + + "1," + + "-1," + + "-9223372036854775808," + + "9223372036854775807]"); + } + + @Test public void numbers() throws IOException { + assumeTrue(factory.supportsBigNumbers()); + + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value(new BigInteger("0")); + writer.value(new BigInteger("9223372036854775808")); + writer.value(new BigInteger("-9223372036854775809")); + writer.value(new BigDecimal("3.141592653589793238462643383")); + writer.endArray(); + writer.close(); + assertThat(factory.json()).isEqualTo("[0," + + "9223372036854775808," + + "-9223372036854775809," + + "3.141592653589793238462643383]"); + } + + @Test public void booleans() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value(true); + writer.value(false); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[true,false]"); + } + + @Test public void boxedBooleans() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value((Boolean) true); + writer.value((Boolean) false); + writer.value((Boolean) null); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[true,false,null]"); + } + + @Test public void nulls() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.nullValue(); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[null]"); + } + + @Test public void strings() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value("a"); + writer.value("a\""); + writer.value("\""); + writer.value(":"); + writer.value(","); + writer.value("\b"); + writer.value("\f"); + writer.value("\n"); + writer.value("\r"); + writer.value("\t"); + writer.value(" "); + writer.value("\\"); + writer.value("{"); + writer.value("}"); + writer.value("["); + writer.value("]"); + writer.value("\0"); + writer.value("\u0019"); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[\"a\"," + + "\"a\\\"\"," + + "\"\\\"\"," + + "\":\"," + + "\",\"," + + "\"\\b\"," + + "\"\\f\"," + + "\"\\n\"," + + "\"\\r\"," + + "\"\\t\"," + + "\" \"," + + "\"\\\\\"," + + "\"{\"," + + "\"}\"," + + "\"[\"," + + "\"]\"," + + "\"\\u0000\"," + + "\"\\u0019\"]"); + } + + @Test public void unicodeLineBreaksEscaped() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.value("\u2028 \u2029"); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[\"\\u2028 \\u2029\"]"); + } + + @Test public void emptyArray() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[]"); + } + + @Test public void emptyObject() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + writer.endObject(); + assertThat(factory.json()).isEqualTo("{}"); + } + + @Test public void objectsInArrays() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.beginObject(); + writer.name("a").value(5); + writer.name("b").value(false); + writer.endObject(); + writer.beginObject(); + writer.name("c").value(6); + writer.name("d").value(true); + writer.endObject(); + writer.endArray(); + assertThat(factory.json()).isEqualTo("[{\"a\":5,\"b\":false}," + + "{\"c\":6,\"d\":true}]"); + } + + @Test public void arraysInObjects() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginObject(); + writer.name("a"); + writer.beginArray(); + writer.value(5); + writer.value(false); + writer.endArray(); + writer.name("b"); + writer.beginArray(); + writer.value(6); + writer.value(true); + writer.endArray(); + writer.endObject(); + assertThat(factory.json()).isEqualTo("{\"a\":[5,false]," + + "\"b\":[6,true]}"); + } + + @Test public void deepNestingArrays() throws IOException { + JsonWriter writer = factory.newWriter(); + for (int i = 0; i < 31; i++) { + writer.beginArray(); + } + for (int i = 0; i < 31; i++) { + writer.endArray(); + } + assertThat(factory.json()) + .isEqualTo("[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"); + } + + @Test public void tooDeepNestingArrays() throws IOException { + JsonWriter writer = factory.newWriter(); + for (int i = 0; i < 31; i++) { + writer.beginArray(); + } + try { + writer.beginArray(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage("Nesting too deep at $[0][0][0][0][0][0][0][0][0][0][0][0][0]" + + "[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]: circular reference?"); + } + } + + @Test public void deepNestingObjects() throws IOException { + JsonWriter writer = factory.newWriter(); + for (int i = 0; i < 31; i++) { + writer.beginObject(); + writer.name("a"); + } + writer.value(true); + for (int i = 0; i < 31; i++) { + writer.endObject(); + } + assertThat(factory.json()).isEqualTo("" + + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" + + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":" + + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":true}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}"); + } + + @Test public void tooDeepNestingObjects() throws IOException { + JsonWriter writer = factory.newWriter(); + for (int i = 0; i < 31; i++) { + writer.beginObject(); + writer.name("a"); + } + try { + writer.beginObject(); + fail(); + } catch (JsonDataException expected) { + assertThat(expected).hasMessage("Nesting too deep at $.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a." + + "a.a.a.a.a.a.a.a.a.a.a.a: circular reference?"); + } + } + + @Test public void lenientWriterPermitsMultipleTopLevelValues() throws IOException { + assumeTrue(factory.supportsMultipleTopLevelValuesInOneDocument()); + + JsonWriter writer = factory.newWriter(); + writer.setLenient(true); + writer.beginArray(); + writer.endArray(); + writer.beginArray(); + writer.endArray(); + writer.close(); + assertThat(factory.json()).isEqualTo("[][]"); + } + + @Test public void strictWriterDoesNotPermitMultipleTopLevelValues() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + try { + writer.beginArray(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void closedWriterThrowsOnStructure() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + writer.close(); + try { + writer.beginArray(); + fail(); + } catch (IllegalStateException expected) { + } + try { + writer.endArray(); + fail(); + } catch (IllegalStateException expected) { + } + try { + writer.beginObject(); + fail(); + } catch (IllegalStateException expected) { + } + try { + writer.endObject(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void closedWriterThrowsOnName() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + writer.close(); + try { + writer.name("a"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void closedWriterThrowsOnValue() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + writer.close(); + try { + writer.value("a"); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void closedWriterThrowsOnFlush() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + writer.close(); + try { + writer.flush(); + fail(); + } catch (IllegalStateException expected) { + } + } + + @Test public void writerCloseIsIdempotent() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.beginArray(); + writer.endArray(); + writer.close(); + writer.close(); + } +} diff --git a/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java b/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java index 4c713f6..0fb1955 100644 --- a/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java +++ b/moshi/src/test/java/com/squareup/moshi/MapJsonAdapterTest.java @@ -105,7 +105,7 @@ public final class MapJsonAdapterTest { fromJson(String.class, Integer.class, "{\"c\":1,\"c\":2}"); fail(); } catch (JsonDataException expected) { - assertThat(expected).hasMessage("Map key 'c' has multiple values at path $.c"); + assertThat(expected).hasMessage("Map key 'c' has multiple values at path $.c: 1 and 2"); } } diff --git a/moshi/src/test/java/com/squareup/moshi/ObjectJsonWriterTest.java b/moshi/src/test/java/com/squareup/moshi/ObjectJsonWriterTest.java new file mode 100644 index 0000000..878ae55 --- /dev/null +++ b/moshi/src/test/java/com/squareup/moshi/ObjectJsonWriterTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.moshi; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.junit.Assert.fail; + +public final class ObjectJsonWriterTest { + @SuppressWarnings("unchecked") + @Test public void array() throws Exception { + ObjectJsonWriter writer = new ObjectJsonWriter(); + + writer.beginArray(); + writer.value("s"); + writer.value(1.5d); + writer.value(true); + writer.nullValue(); + writer.endArray(); + + assertThat((List) writer.root()).containsExactly("s", 1.5d, true, null); + } + + @Test public void object() throws Exception { + ObjectJsonWriter writer = new ObjectJsonWriter(); + writer.setSerializeNulls(true); + + writer.beginObject(); + writer.name("a").value("s"); + writer.name("b").value(1.5d); + writer.name("c").value(true); + writer.name("d").nullValue(); + writer.endObject(); + + assertThat((Map) writer.root()).containsExactly( + entry("a", "s"), entry("b", 1.5d), entry("c", true), entry("d", null)); + } + + @Test public void repeatedNameThrows() throws IOException { + ObjectJsonWriter writer = new ObjectJsonWriter(); + writer.beginObject(); + writer.name("a").value(1L); + try { + writer.name("a").value(2L); + fail(); + } catch (IllegalArgumentException expected) { + assertThat(expected).hasMessage( + "Map key 'a' has multiple values at path $.a: 1 and 2"); + } + } +} +