使用GSON反序列化为ImmutableMap

时间:2012-11-29 10:00:56

标签: java serialization gson

我想用GSON来推广:

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

...进入番石榴ImmutableMap

private ImmutableMap<String,Integer> starterItems;

我以为我只是使用常规的GSON映射解析,然后制作结果的不可变副本,如下所示:

    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));
        }
    });

但正如预期的那样,这太简单了(没有类型信息)。我收到错误:

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

我能做我想做的事吗?

6 个答案:

答案 0 :(得分:7)

这并不是那么简单,因为您需要维护类型参数以构建包含正确类型的地图。为此,您可以使用TypeAdapterFactory,并使用完全指定的TypeAdapter向其请求代理人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));
            }
        };
    }
}

现在您还有另一个问题:默认的GSON MapTypeAdapterFactory将尝试创建ImmutableMap的实例,并对其进行修改。这显然是行不通的。您应该从TypeToken<HashMap<K, V>>创建TypeToken<ImmutableMap<K, V>>,但老实说我不知道​​如何做到这一点。相反,当实际需要InstanceCreator时,您可以使用HashMap来诱骗GSON构建ImmutableMap

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>();
        }
    };
}

显然,您必须同时注册TypeAdapterFactory和InstanceCreator:

GsonBuilder b = new GsonBuilder();
b.registerTypeAdapterFactory(new ImmutableMapTypeAdapterFactory());
b.registerTypeAdapter(ImmutableMap.class, ImmutableMapTypeAdapterFactory.newCreator());

答案 1 :(得分:2)

这也适用于ImmutableMap:https://stackoverflow.com/a/7773201/342947

public final static class ImmutableMapDeserializer implements JsonDeserializer<ImmutableMap<?,?>> {
    @Override
    public ImmutableMap<?,?> deserialize(final JsonElement json, final Type type,
                                       final JsonDeserializationContext context) throws JsonParseException
    {
        final Type type2 =
            ParameterizedTypeImpl.make(Map.class, ((ParameterizedType) type).getActualTypeArguments(), null);
        final Map<?,?> map = context.deserialize(json, type2);
        return ImmutableMap.copyOf(map);
    }
}

gBuilder.registerTypeAdapter(ImmutableMap.class, new GsonUtils.ImmutableMapDeserializer());

答案 2 :(得分:1)

当您添加所需的类型信息时,它会起作用:

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

但是,这需要为应支持的所有类型的地图复制该代码。在这方面,弗拉维奥的答案可能更为通用。

答案 3 :(得分:1)

Just基于Flavio的答案,但是采用快速的两步复制和粘贴格式。

  1. 创建GsonUtils课程(我已在下面提及)

  2. new Gson()的所有实例替换为GsonUtils.get()

  3. GsonUtils.java

    package com.innomatixdata.busscan.utils;
    
    import android.support.annotation.Nullable;
    
    import com.google.common.collect.ImmutableMap;
    import com.google.gson.Gson;
    import com.google.gson.GsonBuilder;
    import com.google.gson.InstanceCreator;
    import com.google.gson.TypeAdapter;
    import com.google.gson.TypeAdapterFactory;
    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.util.HashMap;
    import java.util.Map;
    
    /**
     * Use this class to get an instance of Gson that is configured for this application.
     */
    public class GsonUtils {
    
        private static final GsonBuilder GSON_BUILDER = new GsonBuilder();
    
        static {
            GSON_BUILDER.registerTypeAdapterFactory(new ImmutableMapTypeAdapterFactory());
            GSON_BUILDER.registerTypeAdapter(ImmutableMap.class, ImmutableMapTypeAdapterFactory.newCreator());
        }
    
        public static Gson get() {
            return GSON_BUILDER.create();
        }
    
        public static class ImmutableMapTypeAdapterFactory implements TypeAdapterFactory {
    
            @Nullable
            @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));
                    }
                };
            }
    
            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>();
                    }
                };
            }
        }
    }
    

答案 4 :(得分:1)

Google的Caliper项目(Java的微型基准测试程序)有一个: https://github.com/google/caliper/blob/master/caliper/src/main/java/com/google/caliper/json/ImmutableMapTypeAdapterFactory.java

参考(如果它移动,或将来删除):

/**
 * Serializes and deserializes {@link ImmutableMap} instances using a {@link HashMap} as an
 * intermediary.
 */
final class ImmutableMapTypeAdapterFactory implements TypeAdapterFactory {
  @SuppressWarnings("unchecked")
  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
    Type type = typeToken.getType();
    if (typeToken.getRawType() != ImmutableMap.class
        || !(type instanceof ParameterizedType)) {
      return null;
    }

    com.google.common.reflect.TypeToken<ImmutableMap<?, ?>> betterToken =
        (com.google.common.reflect.TypeToken<ImmutableMap<?, ?>>)
            com.google.common.reflect.TypeToken.of(typeToken.getType());
    final TypeAdapter<HashMap<?, ?>> hashMapAdapter =
        (TypeAdapter<HashMap<?, ?>>) gson.getAdapter(
            TypeToken.get(betterToken.getSupertype(Map.class).getSubtype(HashMap.class)
                .getType()));
    return new TypeAdapter<T>() {
      @Override public void write(JsonWriter out, T value) throws IOException {
        HashMap<?, ?> hashMap = Maps.newHashMap((Map<?, ?>) value);
        hashMapAdapter.write(out, hashMap);
      }

      @Override public T read(JsonReader in) throws IOException {
        HashMap<?, ?> hashMap = hashMapAdapter.read(in);
        return (T) ImmutableMap.copyOf(hashMap);
      }
    };
  }
}

答案 5 :(得分:0)

要添加Flavio的答案,可以更改传递给getDelegateAdapter()的TypeToken的原始类型,从而不需要InstanceCreator。

final TypeToken<T> mapType = (TypeToken<T>) TypeToken.getParameterized(Map.class,
        ((ParameterizedType) type.getType()).getActualTypeArguments());
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, mapType);