diff --git a/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java b/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java new file mode 100644 index 0000000..5df57a0 --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/CollectionJsonAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 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.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** Converts collection types to JSON arrays containing their converted contents. */ +abstract class CollectionJsonAdapter, T> extends JsonAdapter { + public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() { + @Override public JsonAdapter create(Type type, AnnotatedElement annotations, Moshi moshi) { + Class rawType = Types.getRawType(type); + if (rawType == List.class || rawType == Collection.class) { + return newArrayListAdapter(type, annotations, moshi).nullSafe(); + } else if (rawType == Set.class) { + return newLinkedHashSetAdapter(type, annotations, moshi).nullSafe(); + } + return null; + } + }; + + private final JsonAdapter elementAdapter; + + private CollectionJsonAdapter(JsonAdapter elementAdapter) { + this.elementAdapter = elementAdapter; + } + + private static JsonAdapter> newArrayListAdapter( + Type type, AnnotatedElement annotations, Moshi moshi) { + Type elementType = Types.collectionElementType(type, Collection.class); + JsonAdapter elementAdapter = moshi.adapter(elementType, annotations); + return new CollectionJsonAdapter, T>(elementAdapter) { + @Override Collection newCollection() { + return new ArrayList(); + } + }; + } + + private static JsonAdapter> newLinkedHashSetAdapter( + Type type, AnnotatedElement annotations, Moshi moshi) { + Type elementType = Types.collectionElementType(type, Collection.class); + JsonAdapter elementAdapter = moshi.adapter(elementType, annotations); + return new CollectionJsonAdapter, T>(elementAdapter) { + @Override Set newCollection() { + return new LinkedHashSet(); + } + }; + } + + abstract C newCollection(); + + @Override public C fromJson(JsonReader reader) throws IOException { + C result = newCollection(); + reader.beginArray(); + while (reader.hasNext()) { + result.add(elementAdapter.fromJson(reader)); + } + reader.endArray(); + return result; + } + + @Override public void toJson(JsonWriter writer, C value) throws IOException { + writer.beginArray(); + for (T element : value) { + elementAdapter.toJson(writer, element); + } + writer.endArray(); + } +} diff --git a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java index 8451dc8..4ba57d7 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java @@ -19,7 +19,6 @@ import java.io.Closeable; import java.io.Flushable; import java.io.IOException; import okio.BufferedSink; -import okio.Okio; import okio.Sink; import static com.squareup.moshi.JsonScope.DANGLING_NAME; @@ -183,11 +182,11 @@ public class JsonWriter implements Closeable, Flushable { /** * Creates a new instance that writes a JSON-encoded stream to {@code sink}. */ - public JsonWriter(Sink sink) { + public JsonWriter(BufferedSink sink) { if (sink == null) { throw new NullPointerException("sink == null"); } - this.sink = Okio.buffer(sink); + this.sink = sink; } /** diff --git a/moshi/src/main/java/com/squareup/moshi/Moshi.java b/moshi/src/main/java/com/squareup/moshi/Moshi.java index 08e93d0..1d31c68 100644 --- a/moshi/src/main/java/com/squareup/moshi/Moshi.java +++ b/moshi/src/main/java/com/squareup/moshi/Moshi.java @@ -30,7 +30,8 @@ public final class Moshi { private Moshi(Builder builder) { List factories = new ArrayList(); factories.addAll(builder.factories); - factories.add(new StandardJsonAdapterFactory()); + factories.add(StandardJsonAdapters.FACTORY); + factories.add(CollectionJsonAdapter.FACTORY); this.factories = Collections.unmodifiableList(factories); } diff --git a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapterFactory.java b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java similarity index 73% rename from moshi/src/main/java/com/squareup/moshi/StandardJsonAdapterFactory.java rename to moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java index 73261bf..2efd3e6 100644 --- a/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapterFactory.java +++ b/moshi/src/main/java/com/squareup/moshi/StandardJsonAdapters.java @@ -19,7 +19,22 @@ import java.io.IOException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Type; -final class StandardJsonAdapterFactory implements JsonAdapter.Factory { +final class StandardJsonAdapters { + public static final JsonAdapter.Factory FACTORY = new JsonAdapter.Factory() { + @Override public JsonAdapter create( + Type type, AnnotatedElement annotations, Moshi moshi) { + // TODO: support all 8 primitive types. + if (type == boolean.class) return BOOLEAN_JSON_ADAPTER; + if (type == double.class) return DOUBLE_JSON_ADAPTER; + if (type == int.class) return INTEGER_JSON_ADAPTER; + if (type == Boolean.class) return BOOLEAN_JSON_ADAPTER.nullSafe(); + if (type == Double.class) return DOUBLE_JSON_ADAPTER.nullSafe(); + if (type == Integer.class) return INTEGER_JSON_ADAPTER.nullSafe(); + if (type == String.class) return STRING_JSON_ADAPTER.nullSafe(); + return null; + } + }; + static final JsonAdapter BOOLEAN_JSON_ADAPTER = new JsonAdapter() { @Override public Boolean fromJson(JsonReader reader) throws IOException { return reader.nextBoolean(); @@ -55,17 +70,4 @@ final class StandardJsonAdapterFactory implements JsonAdapter.Factory { writer.value(value); } }; - - @Override public JsonAdapter create( - Type type, AnnotatedElement annotations, Moshi moshi) { - // TODO: support all 8 primitive types. - if (type == boolean.class) return BOOLEAN_JSON_ADAPTER; - if (type == double.class) return DOUBLE_JSON_ADAPTER; - if (type == int.class) return INTEGER_JSON_ADAPTER; - if (type == Boolean.class) return BOOLEAN_JSON_ADAPTER.nullSafe(); - if (type == Double.class) return DOUBLE_JSON_ADAPTER.nullSafe(); - if (type == Integer.class) return INTEGER_JSON_ADAPTER.nullSafe(); - if (type == String.class) return STRING_JSON_ADAPTER.nullSafe(); - return null; - } } diff --git a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java index 86719c8..1dfeb64 100644 --- a/moshi/src/test/java/com/squareup/moshi/MoshiTest.java +++ b/moshi/src/test/java/com/squareup/moshi/MoshiTest.java @@ -18,8 +18,15 @@ package com.squareup.moshi; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; import java.lang.reflect.Type; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Locale; +import java.util.Set; import org.junit.Test; import static java.lang.annotation.RetentionPolicy.RUNTIME; @@ -106,6 +113,52 @@ public final class MoshiTest { assertThat(adapter.fromJson("\"b\"")).isEqualTo("B"); } + @Test public void listJsonAdapter() throws Exception { + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter> adapter = moshi.adapter(new TypeLiteral>() { + }.getType()); + assertThat(adapter.toJson(Arrays.asList("a", "b"))).isEqualTo("[\"a\",\"b\"]"); + assertThat(adapter.fromJson("[\"a\",\"b\"]")).isEqualTo(Arrays.asList("a", "b")); + } + + @Test public void setJsonAdapter() throws Exception { + Set set = new LinkedHashSet(); + set.add("a"); + set.add("b"); + + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter> adapter = moshi.adapter(new TypeLiteral>() {}.getType()); + assertThat(adapter.toJson(set)).isEqualTo("[\"a\",\"b\"]"); + assertThat(adapter.fromJson("[\"a\",\"b\"]")).isEqualTo(set); + } + + @Test public void collectionJsonAdapter() throws Exception { + Collection collection = new ArrayDeque(); + collection.add("a"); + collection.add("b"); + + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter> adapter = moshi.adapter( + new TypeLiteral>() {}.getType()); + assertThat(adapter.toJson(collection)).isEqualTo("[\"a\",\"b\"]"); + assertThat(adapter.fromJson("[\"a\",\"b\"]")).containsExactly("a", "b"); + } + + @Uppercase + static List uppercaseStrings; + + @Test public void collectionsKeepAnnotations() throws Exception { + Moshi moshi = new Moshi.Builder() + .add(new UppercaseAdapterFactory()) + .build(); + + Field uppercaseStringsField = MoshiTest.class.getDeclaredField("uppercaseStrings"); + JsonAdapter> adapter = moshi.adapter(uppercaseStringsField.getGenericType(), + uppercaseStringsField); + assertThat(adapter.toJson(Arrays.asList("a"))).isEqualTo("[\"A\"]"); + assertThat(adapter.fromJson("[\"b\"]")).isEqualTo(Arrays.asList("B")); + } + static class Pizza { final int diameter; final boolean extraCheese;