12

我想使用 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

我可以做我想做的事吗?

4

6 回答 6

8

这不是那么简单,因为您需要维护类型参数以构建包含正确类型的映射。为此,您可以使用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));
            }
        };
    }
}

现在你有另一个问题:默认的 GSONMapTypeAdapterFactory会尝试创建一个 的实例ImmutableMap,并对其进行修改。这显然行不通。TypeToken<HashMap<K, V>>你应该从 中创建一个TypeToken<ImmutableMap<K, V>>,但老实说我不知道​​你怎么能做到这一点。相反,您可以使用 anInstanceCreator来欺骗 GSON 在实际需要HashMapan 时构建 a: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());
于 2012-11-29T10:49:01.730 回答
5

Google 的 Caliper 项目(Java 的 micro-benchmarker)有一个: 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);
      }
    };
  }
}
于 2015-12-06T09:26:04.520 回答
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());
于 2013-05-07T11:41:50.443 回答
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()));
            }
        });

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

于 2012-11-29T12:36:30.387 回答
1

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

  1. 创建GsonUtils类(我在下面包含)

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

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>();
                }
            };
        }
    }
}
于 2015-11-20T20:51:05.900 回答
0

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

final TypeToken<T> mapType = (TypeToken<T>) TypeToken.getParameterized(Map.class,
        ((ParameterizedType) type.getType()).getActualTypeArguments());
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, mapType);
于 2017-03-01T22:49:51.787 回答