Be backwards-compatible for serialization of maps whose keys aren't primitives.
This commit is contained in:
parent
b892c85909
commit
194c18d20c
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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>();
|
||||
|
Loading…
Reference in New Issue
Block a user