diff --git a/moshi/src/main/java/com/squareup/moshi/JsonReader.java b/moshi/src/main/java/com/squareup/moshi/JsonReader.java index ecd95cb..76e0531 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonReader.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonReader.java @@ -429,6 +429,7 @@ public abstract class JsonReader implements Closeable { * * @throws JsonDataException if the next token is not a literal value, if a JSON object has a * duplicate key. + * @see JsonWriter#jsonValue(Object) */ public final @Nullable Object readJsonValue() throws IOException { switch (peek()) { diff --git a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java index f5d4978..af39020 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java @@ -19,6 +19,8 @@ import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import java.util.Arrays; +import java.util.List; +import java.util.Map; import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; import okio.BufferedSink; @@ -386,6 +388,58 @@ public abstract class JsonWriter implements Closeable, Flushable { @CheckReturnValue public abstract BufferedSink valueSink() throws IOException; + /** + * Encodes the value which may be a string, number, boolean, null, map, or list. + * + * @return this writer. + * @see JsonReader#readJsonValue() + */ + public final JsonWriter jsonValue(@Nullable Object value) throws IOException { + if (value instanceof Map) { + beginObject(); + for (Map.Entry entry : ((Map) value).entrySet()) { + Object key = entry.getKey(); + if (!(key instanceof String)) { + throw new IllegalArgumentException(key == null + ? "Map keys must be non-null" + : "Map keys must be of type String: " + key.getClass().getName()); + } + name(((String) key)); + jsonValue(entry.getValue()); + } + endObject(); + + } else if (value instanceof List) { + beginArray(); + for (Object element : ((List) value)) { + jsonValue(element); + } + endArray(); + + } else if (value instanceof String) { + value(((String) value)); + + } else if (value instanceof Boolean) { + value(((Boolean) value).booleanValue()); + + } else if (value instanceof Double) { + value(((Double) value).doubleValue()); + + } else if (value instanceof Long) { + value(((Long) value).longValue()); + + } else if (value instanceof Number) { + value(((Number) value)); + + } else if (value == null) { + nullValue(); + + } else { + throw new IllegalArgumentException("Unsupported type: " + value.getClass().getName()); + } + return this; + } + /** * Changes the writer to treat the next value as a string name. This is useful for map adapters so * that arbitrary type adapters can use {@link #value} to write a name value. diff --git a/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java b/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java index 6fcfcd5..692ea51 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonWriterTest.java @@ -18,7 +18,11 @@ package com.squareup.moshi; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import okio.BufferedSink; import org.junit.Test; import org.junit.runner.RunWith; @@ -742,4 +746,72 @@ public final class JsonWriterTest { assertThat(factory.json()).isEqualTo("{\"a\":1.0}"); sink.close(); } + + @Test public void jsonValueTypes() throws IOException { + JsonWriter writer = factory.newWriter(); + writer.setSerializeNulls(true); + + writer.beginArray(); + writer.jsonValue(null); + writer.jsonValue(1.1d); + writer.jsonValue(1L); + writer.jsonValue(1); + writer.jsonValue(true); + writer.jsonValue("one"); + writer.jsonValue(Collections.emptyList()); + writer.jsonValue(Arrays.asList(1, 2, null, 3)); + writer.jsonValue(Collections.emptyMap()); + Map map = new LinkedHashMap<>(); + map.put("one", "uno"); + map.put("two", null); + writer.jsonValue(map); + writer.endArray(); + + assertThat(factory.json()).isEqualTo("[" + + "null," + + "1.1," + + "1," + + "1," + + "true," + + "\"one\"," + + "[]," + + "[1,2,null,3]," + + "{}," + + "{\"one\":\"uno\",\"two\":null}" + + "]"); + } + + @Test public void jsonValueIllegalTypes() throws IOException { + try { + factory.newWriter().jsonValue(new Object()); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Unsupported type: java.lang.Object"); + } + + try { + factory.newWriter().jsonValue('1'); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Unsupported type: java.lang.Character"); + } + + Map mapWrongKey = new LinkedHashMap<>(); + mapWrongKey.put(1, "one"); + try { + factory.newWriter().jsonValue(mapWrongKey); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Map keys must be of type String: java.lang.Integer"); + } + + Map mapNullKey = new LinkedHashMap<>(); + mapNullKey.put(null, "one"); + try { + factory.newWriter().jsonValue(mapNullKey); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Map keys must be non-null"); + } + } }