Bart van Heukelom Bart van Heukelom -4 years ago 215
Java Question

Deserialize to an ImmutableMap with GSON

I'd like to use GSON to derialize:

"starterItems": {
"Appeltaart": 3,
"Soap_50": 3
}


...into a Guava
ImmutableMap
:

private ImmutableMap<String,Integer> starterItems;


I thought I'd just use regular GSON map parsing, then make an immutable copy of the result, like this:

gb.registerTypeAdapter(ImmutableMap.class, new JsonDeserializer<ImmutableMap>() {
@SuppressWarnings("unchecked")
@Override public ImmutableMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return ImmutableMap.copyOf((Map) context.deserialize(json, Map.class));
}
});


But as expected, that was too simple (there is no type information). I get the error:

com.google.gson.JsonParseException: The JsonDeserializer MapTypeAdapter failed to deserialized json object {"Appeltaart":3,"Soap_50":3} given the type interface java.util.Map


Can I do what I want?

Answer Source

It's not that straightforward, since you would want to maintain the type parameters in order to build a map containing the right types. To do that, you can go with a TypeAdapterFactory, and ask there for a delegate TypeAdapter, using the fully specified TypeToken.

public class ImmutableMapTypeAdapterFactory implements TypeAdapterFactory {

    public static final ImmutableMapTypeAdapterFactory INSTANCE = new ImmutableMapTypeAdapterFactory();

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        if (!ImmutableMap.class.isAssignableFrom(type.getRawType())) {
            return null;
        }
        final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
        return new TypeAdapter<T>() {
            @Override
            public void write(JsonWriter out, T value) throws IOException {
                delegate.write(out, value);
            }

            @Override
            @SuppressWarnings("unchecked")
            public T read(JsonReader in) throws IOException {
                return (T) ImmutableMap.copyOf((Map) delegate.read(in));
            }
        };
    }
}

Now you have another problem: the default GSON MapTypeAdapterFactory will try to create an instance of ImmutableMap, and modify it. This obviously won't work. You should create a TypeToken<HashMap<K, V>> from the TypeToken<ImmutableMap<K, V>>, but I honestly don't know how you can do that. Instead, you can use an InstanceCreator to trick GSON into building a HashMap when an ImmutableMap is actually required:

public static <K,V> InstanceCreator<Map<K, V>> newCreator() {
    return new InstanceCreator<Map<K, V>>() {
        @Override
        public Map<K, V> createInstance(Type type) {
            return new HashMap<K, V>();
        }
    };
}

Clearly you have to register both the TypeAdapterFactory and the InstanceCreator:

GsonBuilder b = new GsonBuilder();
b.registerTypeAdapterFactory(new ImmutableMapTypeAdapterFactory());
b.registerTypeAdapter(ImmutableMap.class, ImmutableMapTypeAdapterFactory.newCreator());
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download