-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add (de-)serialization support for Iterable
This adds only support for (de-)serializing Iterable, but not any of its subtypes since it would not be possible to properly deserialize them again.
- Loading branch information
1 parent
ceae88b
commit 97175ce
Showing
4 changed files
with
190 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
gson/src/main/java/com/google/gson/internal/bind/IterableTypeAdapterFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.google.gson.internal.bind; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.TypeAdapterFactory; | ||
import com.google.gson.internal.$Gson$Types; | ||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonToken; | ||
import com.google.gson.stream.JsonWriter; | ||
import java.io.IOException; | ||
import java.lang.reflect.Type; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public final class IterableTypeAdapterFactory implements TypeAdapterFactory { | ||
public static final IterableTypeAdapterFactory INSTANCE = new IterableTypeAdapterFactory(); | ||
|
||
private IterableTypeAdapterFactory() { | ||
} | ||
|
||
@Override | ||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { | ||
Type type = typeToken.getType(); | ||
|
||
Class<? super T> rawType = typeToken.getRawType(); | ||
/* | ||
* Only support Iterable, but not subtypes | ||
* This allows freely choosing Iterable implementation on deserialization | ||
*/ | ||
if (rawType != Iterable.class) { | ||
return null; | ||
} | ||
|
||
Type elementType = $Gson$Types.getIterableElementType(type, rawType); | ||
TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType)); | ||
|
||
@SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter | ||
TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter); | ||
return result; | ||
} | ||
|
||
private static final class Adapter<E> extends TypeAdapter<Iterable<E>> { | ||
private final TypeAdapter<E> elementTypeAdapter; | ||
|
||
public Adapter(Gson context, Type elementType, TypeAdapter<E> elementTypeAdapter) { | ||
this.elementTypeAdapter = | ||
new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType); | ||
} | ||
|
||
@Override public Iterable<E> read(JsonReader in) throws IOException { | ||
if (in.peek() == JsonToken.NULL) { | ||
in.nextNull(); | ||
return null; | ||
} | ||
|
||
List<E> list = new ArrayList<E>(); | ||
in.beginArray(); | ||
while (in.hasNext()) { | ||
E instance = elementTypeAdapter.read(in); | ||
list.add(instance); | ||
} | ||
in.endArray(); | ||
return list; | ||
} | ||
|
||
@Override public void write(JsonWriter out, Iterable<E> iterable) throws IOException { | ||
if (iterable == null) { | ||
out.nullValue(); | ||
return; | ||
} | ||
|
||
out.beginArray(); | ||
for (E element : iterable) { | ||
elementTypeAdapter.write(out, element); | ||
} | ||
out.endArray(); | ||
} | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
gson/src/test/java/com/google/gson/functional/IterableTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package com.google.gson.functional; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.google.gson.TypeAdapter; | ||
import com.google.gson.reflect.TypeToken; | ||
import com.google.gson.stream.JsonReader; | ||
import com.google.gson.stream.JsonWriter; | ||
|
||
import junit.framework.TestCase; | ||
|
||
public class IterableTest extends TestCase { | ||
private static class CustomIterable implements Iterable<Integer> { | ||
final int base; | ||
|
||
public CustomIterable(int base) { | ||
this.base = base; | ||
} | ||
|
||
@Override | ||
public Iterator<Integer> iterator() { | ||
return Arrays.asList(base + 1, base + 2).iterator(); | ||
} | ||
} | ||
|
||
public void testSerialize() { | ||
CustomIterable iterable = new CustomIterable(0); | ||
|
||
Gson gson = new Gson(); | ||
// Serializing specific Iterable subtype should use reflection-based approach | ||
assertEquals("{\"base\":0}", gson.toJson(iterable)); | ||
// But serializing as Iterable should use adapter | ||
assertEquals("[1,2]", gson.toJson(iterable, Iterable.class)); | ||
} | ||
|
||
public void testDeserialize() { | ||
Gson gson = new Gson(); | ||
// Deserializing as specific Iterable subtype should use reflection-based approach | ||
// i.e. must not choose any (potentially incompatible) class implementing `Iterable` | ||
// See also https://github.com/google/gson/issues/1708 | ||
assertEquals(1, gson.fromJson("{\"base\":1}", CustomIterable.class).base); | ||
|
||
// But deserializing as Iterable should use adapter | ||
Iterable<Integer> deserialized = gson.fromJson("[1,2]", new TypeToken<Iterable<Integer>>() {}.getType()); | ||
// Collect elements and then compare them to not make any assumptions about | ||
// type of `deserialized` | ||
ArrayList<Integer> elements = new ArrayList<Integer>(); | ||
for (Integer element : deserialized) { | ||
elements.add(element); | ||
} | ||
assertEquals(Arrays.asList(1, 2), elements); | ||
} | ||
|
||
private static class CustomIterableTypeAdapter extends TypeAdapter<Iterable<?>> { | ||
private static final int SERIALIZED = 5; | ||
|
||
private static List<Integer> getDeserialized() { | ||
return Arrays.asList(1, 2); | ||
} | ||
|
||
@Override public void write(JsonWriter out, Iterable<?> value) throws IOException { | ||
out.value(SERIALIZED); | ||
} | ||
|
||
@Override public Iterable<?> read(JsonReader in) throws IOException { | ||
in.skipValue(); | ||
return getDeserialized(); | ||
} | ||
} | ||
|
||
/** | ||
* Verify that overwriting built-in {@link Iterable} adapter is possible. | ||
*/ | ||
public void testCustomAdapter() { | ||
Gson gson = new GsonBuilder() | ||
.registerTypeAdapter(Iterable.class, new CustomIterableTypeAdapter()) | ||
.create(); | ||
|
||
String expectedJson = String.valueOf(CustomIterableTypeAdapter.SERIALIZED); | ||
assertEquals(expectedJson, gson.toJson(new ArrayList<String>(), Iterable.class)); | ||
|
||
List<Integer> expectedDeserialized = CustomIterableTypeAdapter.getDeserialized(); | ||
assertEquals(expectedDeserialized, gson.fromJson("[]", Iterable.class)); | ||
} | ||
} |