"features": { "entry": [ { "key": "key1", "value": "value1" }, { "key": "key2", "value": "value2" }, { "key": "key3, "value": "value3" } ] }
But I stuck on only trying to make Gson to work with a custom MapTypeAdapterFactory. This is what I tried:
import com.google.gson.*
import com.google.gson.internal.*
import com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper
import com.google.gson.internal.bind.TypeAdapters
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.awt.*
import java.lang.reflect.Type
class CustomMapTypeAdapterFactory implements TypeAdapterFactory {
private ConstructorConstructor constructorConstructor;
private boolean complexMapKeySerialization;
public CustomMapTypeAdapterFactory() {}
public CustomMapTypeAdapterFactory(ConstructorConstructor constructorConstructor, boolean complexMapKeySerialization) {
this.constructorConstructor = constructorConstructor;
this.complexMapKeySerialization = complexMapKeySerialization;
}
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class rawType = typeToken.getRawType();
if (!Map.class.isAssignableFrom(rawType)) {
return null;
} else {
Class rawTypeOfSrc = $Gson$Types.getRawType(type);
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
TypeAdapter keyAdapter = this.getKeyAdapter(gson, keyAndValueTypes[0]);
TypeAdapter valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
ObjectConstructor constructor = this.constructorConstructor.get(typeToken);
CustomMapTypeAdapterFactory.Adapter result = new CustomMapTypeAdapterFactory.Adapter(gson, keyAndValueTypes[0], keyAdapter, keyAndValueTypes[1], valueAdapter, constructor);
return result;
}
}
private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
return keyType != Boolean.TYPE && keyType != Boolean.class ? context.getAdapter(TypeToken.get(keyType)) : TypeAdapters.BOOLEAN_AS_STRING;
}
private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
private final TypeAdapter<K> keyTypeAdapter;
private final TypeAdapter<V> valueTypeAdapter;
private final ObjectConstructor<? extends Map<K, V>> constructor;
public Adapter(Gson var1, Type context, TypeAdapter<K> keyType, Type keyTypeAdapter, TypeAdapter<V> valueType, ObjectConstructor<? extends Map<K, V>> valueTypeAdapter) {
this.keyTypeAdapter = new TypeAdapterRuntimeTypeWrapper(context, keyTypeAdapter, keyType);
this.valueTypeAdapter = new TypeAdapterRuntimeTypeWrapper(context, valueTypeAdapter, valueType);
this.constructor = constructor;
}
public Map<K, V> read(JsonReader input) throws IOException {
JsonToken peek =
input.peek();
if (peek == JsonToken.NULL) {
input.nextNull();
return null;
} else {
Map map = (Map) this.constructor.construct();
Object key;
Object value;
Object replaced;
if (peek == JsonToken.BEGIN_ARRAY) {
input.beginArray();
while (input.hasNext()) {
input.beginArray();
key = this.keyTypeAdapter.read(input);
value = this.valueTypeAdapter.read(input);
replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
input.endArray();
}
input.endArray();
} else {
input.beginObject();
while (input.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(input);
key = this.keyTypeAdapter.read(input);
value = this.valueTypeAdapter.read(input);
replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
input.endObject();
}
return map;
}
}
public void write(JsonWriter out, Map<K, V> map) throws IOException {
if (map == null) {
out.nullValue();
} else if (!CustomMapTypeAdapterFactory.this.complexMapKeySerialization) {
out.beginObject();
Iterator var9 = map.entrySet().iterator();
while (var9.hasNext()) {
Map.Entry var10 = (Map.Entry) var9.next();
out.name(String.valueOf(var10.getKey()));
this.valueTypeAdapter.write(out, var10.getValue());
}
out.endObject();
} else {
boolean hasComplexKeys = false;
ArrayList keys = new ArrayList(map.size());
ArrayList values = new ArrayList(map.size());
JsonElement keyElement1;
for (Iterator i = map.entrySet().iterator(); i.hasNext(); hasComplexKeys |= keyElement1.isJsonArray() || keyElement1.isJsonObject()) {
Map.Entry keyElement = (Map.Entry) i.next();
keyElement1 = this.keyTypeAdapter.toJsonTree(keyElement.getKey());
keys.add(keyElement1);
values.add(keyElement.getValue());
}
int var11;
if (hasComplexKeys) {
out.beginArray();
for (var11 = 0; var11 < keys.size(); ++var11) {
out.beginArray();
Streams.write((JsonElement) keys.get(var11), out);
this.valueTypeAdapter.write(out, values.get(var11));
out.endArray();
}
out.endArray();
} else {
out.beginObject();
for (var11 = 0; var11 < keys.size(); ++var11) {
JsonElement var12 = (JsonElement) keys.get(var11);
out.name(this.keyToString(var12));
this.valueTypeAdapter.write(out, values.get(var11));
}
out.endObject();
}
}
}
private String keyToString(JsonElement keyElement) {
if (keyElement.isJsonPrimitive()) {
JsonPrimitive primitive = keyElement.getAsJsonPrimitive();
if (primitive.isNumber()) {
return String.valueOf(primitive.getAsNumber());
} else if (primitive.isBoolean()) {
return Boolean.toString(primitive.getAsBoolean());
} else if (primitive.isString()) {
return primitive.getAsString();
} else {
throw new AssertionError();
}
} else if (keyElement.isJsonNull()) {
return "null";
} else {
throw new AssertionError();
}
}
}
}
ConstructorConstructor constructorConstructor = new ConstructorConstructor(new HashMap<Type, InstanceCreator<?>>());
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new CustomMapTypeAdapterFactory(constructorConstructor, false)).create();
Map<Point, String> original = new HashMap<>();
original.put("1", "a");
original.put("2", "b");
System.out.println(gson.toJson(original));
But I got the following error:
Caught: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper(sun.reflect.generics.reflectiveObjects.TypeVariableImpl, sun.reflect.generics.reflectiveObjects.TypeVariableImpl, com.google.gson.internal.bind.ObjectTypeAdapter)
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper(sun.reflect.generics.reflectiveObjects.TypeVariableImpl, sun.reflect.generics.reflectiveObjects.TypeVariableImpl, com.google.gson.internal.bind.ObjectTypeAdapter)
at CustomMapTypeAdapterFactory$Adapter.<init>(gsonTest.groovy:51)at CustomMapTypeAdapterFactory.create(gsonTest.groovy:36)at com.google.gson.Gson.getAdapter(Gson.java:360)at com.google.gson.Gson.toJson(Gson.java:597)at com.google.gson.Gson.toJson(Gson.java:584)at com.google.gson.Gson.toJson(Gson.java:539)at com.google.gson.Gson.toJson(Gson.java:519)at com.google.gson.Gson$toJson.call(Unknown Source)at gsonTest.run(gsonTest.groovy:184)at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Process finished with exit code 1
Is there any way to override the default processing of maps in Gson?
Thanks
package com.tgifridays.ws.gson.typeadapterfactory;
import com.google.gson.Gson;import com.google.gson.JsonElement;import com.google.gson.JsonPrimitive;import com.google.gson.JsonSyntaxException;import com.google.gson.TypeAdapter;import com.google.gson.TypeAdapterFactory;import com.google.gson.internal.$Gson$Types;import com.google.gson.internal.ConstructorConstructor;import com.google.gson.internal.JsonReaderInternalAccess;import com.google.gson.internal.ObjectConstructor;import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.TypeAdapters;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 org.apache.log4j.Logger;
import java.io.IOException;import java.lang.reflect.Type;import java.util.ArrayList;import java.util.List;import java.util.Map;
/** * @author cbelizon */public class CustomMapTypeAdapterFactory implements TypeAdapterFactory { private static final Logger LOG = Logger.getLogger(CustomMapTypeAdapterFactory.class); private final ConstructorConstructor constructorConstructor; final boolean complexMapKeySerialization;
public CustomMapTypeAdapterFactory(ConstructorConstructor constructorConstructor, boolean complexMapKeySerialization) { this.constructorConstructor = constructorConstructor; this.complexMapKeySerialization = complexMapKeySerialization; }
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) { Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType(); if (!Map.class.isAssignableFrom(rawType)) { return null; }
Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]); TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1])); ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
// we don't define a type parameter for the key or value types TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter, keyAndValueTypes[1], valueAdapter, constructor); return result; }
/** * Returns a type adapter that writes the value as a string. */
private TypeAdapter<?> getKeyAdapter(Gson 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; private final ObjectConstructor<? extends Map<K, V>> constructor;
public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter, Type valueType, TypeAdapter<V> valueTypeAdapter, ObjectConstructor<? extends Map<K, V>> constructor) { this.keyTypeAdapter = new CustomTypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType); this.valueTypeAdapter = new CustomTypeAdapterRuntimeTypeWrapper<V>(context, valueTypeAdapter, valueType); this.constructor = constructor; }
@Override public Map<K, V> read(JsonReader inner) throws IOException { JsonToken peek = inner.peek(); if (peek == JsonToken.NULL) { inner.nextNull(); return null; }
Map<K, V> map = constructor.construct();
if (peek == JsonToken.BEGIN_ARRAY) { inner.beginArray(); while (inner.hasNext()) { inner.beginArray(); // entry array K key = keyTypeAdapter.read(inner); V value = valueTypeAdapter.read(inner); V replaced = map.put(key, value);
if (replaced != null) { throw new JsonSyntaxException("duplicate key: " + key); }
inner.endArray(); } inner.endArray(); } else { inner.beginObject(); while (inner.hasNext()) { JsonReaderInternalAccess.INSTANCE.promoteNameToValue(inner); K key = keyTypeAdapter.read(inner); V value = valueTypeAdapter.read(inner); V replaced = map.put(key, value);
if (replaced != null) { throw new JsonSyntaxException("duplicate key: " + key); } }
inner.endObject(); } return map; }
@Override
public void write(JsonWriter out, Map<K, V> map) throws IOException { if (map == null) { out.nullValue();
return; }
if (!complexMapKeySerialization) { out.beginObject(); out.name("entry"); out.beginArray(); for (Map.Entry<K, V> entry : map.entrySet()) { out.beginObject(); out.name("key").value(String.valueOf(entry.getKey())); out.name("value"); valueTypeAdapter.write(out, entry.getValue()); out.endObject(); } out.endArray(); out.endObject(); return; }
boolean hasComplexKeys = false; List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
List<V> values = new ArrayList<V>(map.size()); for (Map.Entry<K, V> entry : map.entrySet()) { JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey()); keys.add(keyElement); values.add(entry.getValue()); hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject(); }
if (hasComplexKeys) { out.beginArray(); for (int i = 0; i < keys.size(); i++) { out.beginArray(); // entry array Streams.write(keys.get(i), out); valueTypeAdapter.write(out, values.get(i)); out.endArray(); } out.endArray(); } else { out.beginObject(); for (int i = 0; i < keys.size(); i++) { JsonElement keyElement = keys.get(i); out.name(keyToString(keyElement)); valueTypeAdapter.write(out, values.get(i));
} out.endObject(); } }
private String keyToString(JsonElement keyElement) { if (keyElement.isJsonPrimitive()) { JsonPrimitive primitive = keyElement.getAsJsonPrimitive(); if (primitive.isNumber()) { return String.valueOf(primitive.getAsNumber()); } else if (primitive.isBoolean()) { return Boolean.toString(primitive.getAsBoolean()); } else if (primitive.isString()) { return primitive.getAsString(); } else { throw new AssertionError(); } } else if (keyElement.isJsonNull()) { return "null"; } else { throw new AssertionError(); } } }}
package com.tgifridays.ws.gson.typeadapterfactory;
import com.google.gson.Gson;import com.google.gson.TypeAdapter;import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;import com.google.gson.reflect.TypeToken;import com.google.gson.stream.JsonReader;import com.google.gson.stream.JsonWriter;
import java.io.IOException;import java.lang.reflect.Type;import java.lang.reflect.TypeVariable;
/** * @author cbelizon */public class CustomTypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> { private final Gson context; private final TypeAdapter<T> delegate; private final Type type;
CustomTypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) { this.context = context; this.delegate = delegate; this.type = type; }
public T read(JsonReader in) throws IOException { return this.delegate.read(in); }
public void write(JsonWriter out, T value) throws IOException { TypeAdapter chosen = this.delegate; Type runtimeType = this.getRuntimeTypeIfMoreSpecific(this.type, value); if (runtimeType != this.type) { TypeAdapter runtimeTypeAdapter = this.context.getAdapter(TypeToken.get(runtimeType)); if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) { chosen = runtimeTypeAdapter; } else if (!(this.delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) { chosen = this.delegate; } else { chosen = runtimeTypeAdapter; } }
chosen.write(out, value); }
private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) { if (value != null && (type == Object.class || type instanceof TypeVariable || type instanceof Class)) { type = value.getClass(); }
return (Type) type; }}
ConstructorConstructor constructorConstructor = new ConstructorConstructor(new HashMap<Type, InstanceCreator<?>>());
gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapterFactory(new TgifMapTypeAdapterFactory(constructorConstructor, false)).create();