Be backwards-compatible for serialization of maps whose keys aren't primitives.

This commit is contained in:
Jesse Wilson 2011-10-20 04:24:27 +00:00
parent b892c85909
commit 194c18d20c
3 changed files with 110 additions and 11 deletions

View File

@ -16,12 +16,6 @@
package com.google.gson.internal.bind;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
@ -33,6 +27,11 @@ 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;
import java.util.Map;
/**
* Adapts maps to either JSON objects or JSON arrays.
@ -119,7 +118,7 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
TypeAdapter<?> keyAdapter = context.getAdapter(TypeToken.get(keyAndValueTypes[0]));
TypeAdapter<?> keyAdapter = getKeyAdapter(context, keyAndValueTypes[0]);
TypeAdapter<?> valueAdapter = context.getAdapter(TypeToken.get(keyAndValueTypes[1]));
ObjectConstructor<T> constructor = constructorConstructor.getConstructor(typeToken);
@ -129,6 +128,15 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
return result;
}
/**
* Returns a type adapter that writes the value as a string.
*/
private TypeAdapter<?> getKeyAdapter(MiniGson context, Type keyType) {
return (keyType == boolean.class || keyType == Boolean.class)
? TypeAdapters.BOOLEAN_AS_STRING
: context.getAdapter(TypeToken.get(keyType));
}
private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
private final TypeAdapter<K> keyTypeAdapter;
private final TypeAdapter<V> valueTypeAdapter;
@ -188,6 +196,16 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
return;
}
if (!complexMapKeySerialization) {
writer.beginObject();
for (Map.Entry<K, V> entry : map.entrySet()) {
writer.name(String.valueOf(entry.getKey()));
valueTypeAdapter.write(writer, entry.getValue());
}
writer.endObject();
return;
}
boolean hasComplexKeys = false;
List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
@ -199,7 +217,7 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
}
if (complexMapKeySerialization && hasComplexKeys) {
if (hasComplexKeys) {
writer.beginArray();
for (int i = 0; i < keys.size(); i++) {
writer.beginArray(); // entry array

View File

@ -127,6 +127,24 @@ public final class TypeAdapters {
}
};
/**
* Writes a boolean as a string. Useful for map keys, where booleans aren't
* otherwise permitted.
*/
public static final TypeAdapter<Boolean> BOOLEAN_AS_STRING = new TypeAdapter<Boolean>() {
@Override public Boolean read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
return Boolean.valueOf(reader.nextString());
}
@Override public void write(JsonWriter writer, Boolean value) throws IOException {
writer.value(value == null ? "null" : value.toString());
}
};
public static final TypeAdapter.Factory BOOLEAN_FACTORY
= newFactory(boolean.class, Boolean.class, BOOLEAN);

View File

@ -21,21 +21,20 @@ import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import junit.framework.TestCase;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import junit.framework.TestCase;
/**
* Functional test for Json serialization and deserialization for Maps
@ -438,6 +437,70 @@ public class MapTest extends TestCase {
assertEquals(expected, gson.toJson(map));
}
public void testComplexKeysSerialization() {
Map<Point, String> map = new LinkedHashMap<Point, String>();
map.put(new Point(2, 3), "a");
map.put(new Point(5, 7), "b");
String json = "{\"2,3\":\"a\",\"5,7\":\"b\"}";
assertEquals(json, gson.toJson(map, new TypeToken<Map<Point, String>>() {}.getType()));
assertEquals(json, gson.toJson(map, Map.class));
}
public void testComplexKeysDeserialization() {
String json = "{\"2,3\":\"a\",\"5,7\":\"b\"}";
try {
gson.fromJson(json, new TypeToken<Map<Point, String>>() {}.getType());
fail();
} catch (JsonParseException expected) {
}
}
public void testStringKeyDeserialization() {
String json = "{\"2,3\":\"a\",\"5,7\":\"b\"}";
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("2,3", "a");
map.put("5,7", "b");
assertEquals(map, gson.fromJson(json, new TypeToken<Map<String, String>>() {}.getType()));
}
public void testNumberKeyDeserialization() {
String json = "{\"2.3\":\"a\",\"5.7\":\"b\"}";
Map<Double, String> map = new LinkedHashMap<Double, String>();
map.put(2.3, "a");
map.put(5.7, "b");
assertEquals(map, gson.fromJson(json, new TypeToken<Map<Double, String>>() {}.getType()));
}
public void testBooleanKeyDeserialization() {
String json = "{\"true\":\"a\",\"false\":\"b\"}";
Map<Boolean, String> map = new LinkedHashMap<Boolean, String>();
map.put(true, "a");
map.put(false, "b");
assertEquals(map, gson.fromJson(json, new TypeToken<Map<Boolean, String>>() {}.getType()));
}
static class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override public boolean equals(Object o) {
return o instanceof Point && x == ((Point) o).x && y == ((Point) o).y;
}
@Override public int hashCode() {
return x * 37 + y;
}
@Override public String toString() {
return x + "," + y;
}
}
static final class MapClass {
private final Map<String, TestTypes.Base> bases = new HashMap<String, TestTypes.Base>();
private final Map<String, TestTypes.Sub> subs = new HashMap<String, TestTypes.Sub>();