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;
|
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.JsonElement;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.google.gson.JsonSyntaxException;
|
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.JsonReader;
|
||||||
import com.google.gson.stream.JsonToken;
|
import com.google.gson.stream.JsonToken;
|
||||||
import com.google.gson.stream.JsonWriter;
|
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.
|
* 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);
|
Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
|
||||||
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
|
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]));
|
TypeAdapter<?> valueAdapter = context.getAdapter(TypeToken.get(keyAndValueTypes[1]));
|
||||||
ObjectConstructor<T> constructor = constructorConstructor.getConstructor(typeToken);
|
ObjectConstructor<T> constructor = constructorConstructor.getConstructor(typeToken);
|
||||||
|
|
||||||
|
@ -129,6 +128,15 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
|
||||||
return result;
|
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 class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
|
||||||
private final TypeAdapter<K> keyTypeAdapter;
|
private final TypeAdapter<K> keyTypeAdapter;
|
||||||
private final TypeAdapter<V> valueTypeAdapter;
|
private final TypeAdapter<V> valueTypeAdapter;
|
||||||
|
@ -188,6 +196,16 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
|
||||||
return;
|
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;
|
boolean hasComplexKeys = false;
|
||||||
List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
|
List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
|
||||||
|
|
||||||
|
@ -199,7 +217,7 @@ public final class MapTypeAdapterFactory implements TypeAdapter.Factory {
|
||||||
hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
|
hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (complexMapKeySerialization && hasComplexKeys) {
|
if (hasComplexKeys) {
|
||||||
writer.beginArray();
|
writer.beginArray();
|
||||||
for (int i = 0; i < keys.size(); i++) {
|
for (int i = 0; i < keys.size(); i++) {
|
||||||
writer.beginArray(); // entry array
|
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
|
public static final TypeAdapter.Factory BOOLEAN_FACTORY
|
||||||
= newFactory(boolean.class, Boolean.class, BOOLEAN);
|
= newFactory(boolean.class, Boolean.class, BOOLEAN);
|
||||||
|
|
||||||
|
|
|
@ -21,21 +21,20 @@ import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.InstanceCreator;
|
import com.google.gson.InstanceCreator;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.google.gson.JsonSerializationContext;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
import com.google.gson.common.TestTypes;
|
import com.google.gson.common.TestTypes;
|
||||||
import com.google.gson.internal.$Gson$Types;
|
import com.google.gson.internal.$Gson$Types;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functional test for Json serialization and deserialization for Maps
|
* Functional test for Json serialization and deserialization for Maps
|
||||||
|
@ -438,6 +437,70 @@ public class MapTest extends TestCase {
|
||||||
assertEquals(expected, gson.toJson(map));
|
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 {
|
static final class MapClass {
|
||||||
private final Map<String, TestTypes.Base> bases = new HashMap<String, TestTypes.Base>();
|
private final Map<String, TestTypes.Base> bases = new HashMap<String, TestTypes.Base>();
|
||||||
private final Map<String, TestTypes.Sub> subs = new HashMap<String, TestTypes.Sub>();
|
private final Map<String, TestTypes.Sub> subs = new HashMap<String, TestTypes.Sub>();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user