简体   繁体   English

用于异构Multimap的Gson适配器

[英]Gson adapter for a heterogeneous Multimap

I have a Multimap<Class<?>, Object> populated like 我有一个Multimap<Class<?>, Object>填充为

multimap.put(o.getClass, o)

ie, each object gets put in a proper bucket according to its class. 即,根据对象的类别将每个对象放入适当的存储桶中。 I need to serialize and deserialize the multimap using Gson. 我需要使用Gson序列化和反序列化多图。 All the objects belong to simple classes having no type parameters. 所有对象都属于没有类型参数的简单类。 I mean, each of them can be deserialized by using gson.fromJson(json, someClass) ; 我的意思是,每个人都可以使用gson.fromJson(json, someClass)进行反序列化; no TypeToken needed here. 这里不需要TypeToken

If it helps, I could use a TypeToken or whatever as the key; 如果有帮助,我可以使用TypeToken或其他任何键; I don't care. 我不在乎 All used classes subclass a class of mine, if it helps. 如果有帮助,所有使用过的类都将属于我的类。 What I don't want is splitting the multimap into multiple homogeneous lists as there will be tens of them. 我不想将多图分成多个同类列表,因为会有数十个同类列表。 As it's actually an ImmutableMultimap , it'd mean many more lines which I want to avoid. 由于它实际上是ImmutableMultimap ,这意味着我想避免更多的行。

What I've tried: Not worth mentioning. 我尝试过的:不值得一提。 None of the Adapters I wrote or saw does anything similar. 我编写或看到的适配器都没有类似的功能。

If I understand you correctly, you can accomplish such a type adapter relatively easy. 如果我对您的理解正确,则可以相对容易地完成这种类型的适配器。

First, let's create a Multimap type adapter. 首先,让我们创建一个Multimap类型的适配器。 The following Multimap type adapter can work with any multimap, however Class -related keys will be specialized below. 以下Multimap类型适配器可以与任何多图一起使用,但是与Class相关的键将在下面进行专门说明。

final class MultimapTypeAdapter<K, V>
        extends TypeAdapter<Multimap<K, V>> {

    private final Converter<K, String> keyConverter;
    private final Function<? super K, ? extends TypeAdapter<V>> valueTypeAdapterProvider;

    private MultimapTypeAdapter(
            final Converter<K, String> keyConverter,
            final Function<? super K, ? extends TypeAdapter<V>> valueTypeAdapterProvider
    ) {
        this.keyConverter = keyConverter;
        this.valueTypeAdapterProvider = valueTypeAdapterProvider;
    }

    static <K, V> TypeAdapter<Multimap<K, V>> multimapTypeAdapter(
            final Converter<K, String> keyConverter,
            final Function<? super K, ? extends TypeAdapter<V>> valueTypeAdapterProvider
    ) {
        return new MultimapTypeAdapter<>(keyConverter, valueTypeAdapterProvider).nullSafe();
    }

    @Override
    @SuppressWarnings("resource")
    public void write(final JsonWriter jsonWriter, final Multimap<K, V> multimap)
            throws IOException {
        jsonWriter.beginObject();
        for ( final K key : multimap.keySet() ) {
            jsonWriter.name(keyConverter.convert(key));
            final TypeAdapter<? super V> typeAdapter = valueTypeAdapterProvider.apply(key);
            jsonWriter.beginArray();
            for ( final V value : multimap.get(key) ) {
                typeAdapter.write(jsonWriter, value);
            }
            jsonWriter.endArray();
        }
        jsonWriter.endObject();
    }

    @Override
    public Multimap<K, V> read(final JsonReader jsonReader)
            throws IOException {
        final ImmutableMultimap.Builder<K, V> multimapBuilder = new ImmutableMultimap.Builder<>();
        jsonReader.beginObject();
        while ( jsonReader.hasNext() ) {
            final K key = keyConverter.reverse().convert(jsonReader.nextName());
            final TypeAdapter<V> typeAdapter = valueTypeAdapterProvider.apply(key);
            jsonReader.beginArray();
            while ( jsonReader.hasNext() ) {
                final V value = typeAdapter.read(jsonReader);
                multimapBuilder.put(key, value);
            }
            jsonReader.endArray();
        }
        jsonReader.endObject();
        return multimapBuilder.build();
    }

}

Now, you can create a simple Class key converter: The converter is pretty straight-forward and self-descriptive. 现在,您可以创建一个简单的Class密钥转换器:该转换器非常简单明了且具有自我描述性。 More complex converting strategies can be found, for example, here and here (the latter does not support arrays in full). 例如,可以在此处此处找到更复杂的转换策略(后者不完全支持数组)。

final class ClassKeyConverter
        extends Converter<Class<?>, String> {

    private static final Converter<Class<?>, String> classKeyConverter = new ClassKeyConverter();

    private ClassKeyConverter() {
    }

    static Converter<Class<?>, String> classKeyConverter() {
        return classKeyConverter;
    }

    @Override
    protected String doForward(final Class<?> a) {
        return a.toString();
    }

    @Override
    public Class<?> doBackward(final String b) {
        final Class<?> primitiveType = primitiveTypes.get(b);
        if ( primitiveType != null ) {
            return primitiveType;
        }
        final int prefix = b.startsWith(CLASS) ? CLASS.length()
                : b.startsWith(INTERFACE) ? INTERFACE.length()
                : -1;
        if ( prefix >= 0 ) {
            try {
                return Class.forName(b.substring(prefix));
            } catch ( final ClassNotFoundException ex ) {
                throw new RuntimeException(ex);
            }
        }
        throw new IllegalArgumentException(b);
    }

    private static final Map<String, Class<?>> primitiveTypes = ImmutableMap.<String, Class<?>>builder()
            .put("boolean", boolean.class)
            .put("byte", byte.class)
            .put("short", short.class)
            .put("int", int.class)
            .put("long", long.class)
            .put("float", float.class)
            .put("double", double.class)
            .put("char", char.class)
            .build();

    private static final String CLASS = "class ";
    private static final String INTERFACE = "interface ";

}

And now you can create a type adapter factory that can process such a multimap: 现在,您可以创建可以处理这种多图的类型适配器工厂:

final class ClassKeyMultimapTypeAdapterFactory
        implements TypeAdapterFactory {

    private static final TypeAdapterFactory classKeyMultimapTypeAdapterFactory = new ClassKeyMultimapTypeAdapterFactory();

    static final Type classKeyMultimapType = TypeToken.getParameterized(Multimap.class, Class.class, Object.class).getType();

    private ClassKeyMultimapTypeAdapterFactory() {
    }

    static TypeAdapterFactory classKeyMultimapTypeAdapterFactory() {
        return classKeyMultimapTypeAdapterFactory;
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        if ( !isClassKeyMultimap(typeToken) ) {
            return null;
        }
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) multimapTypeAdapter(classKeyConverter(), type -> gson.getDelegateAdapter(this, TypeToken.get(type)));
        return typeAdapter;
    }

    private static boolean isClassKeyMultimap(final TypeToken<?> typeToken) {
        if ( Multimap.class.isAssignableFrom(typeToken.getRawType()) ) {
            final Type type = typeToken.getType();
            if ( type instanceof ParameterizedType ) {
                final ParameterizedType parameterizedType = (ParameterizedType) type;
                if ( Class.class.equals(parameterizedType.getActualTypeArguments()[0]) ) {
                    // We expect to process `Multimap<Class<?>, ?>` only
                    return true;
                }
            }
        }
        return false;
    }

}

Finally, you can test it out: 最后,您可以对其进行测试:

private static final Gson gson = new GsonBuilder()
        .disableHtmlEscaping()
        .registerTypeAdapterFactory(classKeyMultimapTypeAdapterFactory())
        .create();

public static void main(final String... args) {
    final Multimap<Class<?>, Object> multimapBefore = ImmutableMultimap.<Class<?>, Object>builder()
            .put(int.class, 2)
            .put(int.class, 3)
            .put(int.class, 4)
            .put(Integer.class, 2)
            .put(Integer.class, 3)
            .put(Integer.class, 4)
            .put(String.class, "foo")
            .put(String.class, "bar")
            .put(String.class, "baz")
            .build();
    System.out.println(multimapBefore);
    final String json = gson.toJson(multimapBefore, classKeyMultimapType);
    System.out.println(json);
    final Multimap<Class<?>, Object> multimapAfter = gson.fromJson(json, classKeyMultimapType);
    System.out.println(multimapAfter);
    if ( !multimapBefore.equals(multimapAfter) ) {
        throw new AssertionError("multimaps do not equal");
    }
}

Output: 输出:

{int=[2, 3, 4], class java.lang.Integer=[2, 3, 4], class java.lang.String=[foo, bar, baz]}
{"int":[2,3,4],"class java.lang.Integer":[2,3,4],"class java.lang.String":["foo","bar","baz"]}
{int=[2, 3, 4], class java.lang.Integer=[2, 3, 4], class java.lang.String=[foo, bar, baz]}

Update 1 更新1

Ok, let's proceed making the isClassKeyMultimap method a bit smarter. 好的,让我们继续使isClassKeyMultimap方法更智能。

I'd like to work with a Multimap<Class<Something>, Something> , too. 我也想使用Multimap<Class<Something>, Something>

You're, I guess, are talking about TypeToken literals. 我猜您在谈论TypeToken文字。 Yep, I used TypeToken.getParameterized(...) forgetting that a Class instance can be parameterized as well. 是的,我使用TypeToken.getParameterized(...)忘记了Class实例也可以被参数化。 All you have to do to make it smarter is just adding an additional check to the method. 使它变得更智能所需要做的就是向该方法添加其他检查。

if ( Multimap.class.isAssignableFrom(typeToken.getRawType()) ) {
    final Type type = typeToken.getType();
    if ( type instanceof ParameterizedType ) {
        final ParameterizedType parameterizedType = (ParameterizedType) type;
        final Type actualTypeArg0 = parameterizedType.getActualTypeArguments()[0];
        // raw java.lang.Class (Class.class, Class.forName("java.lang.Class"), etc)
        if ( actualTypeArg0 == Class.class ) {
            return true;
        }
        // or maybe it's something like a Class<...> instance that:
        // * can be generated by javac when you parameterize a type (this is why Gson TypeToken's look "weird")
        // * or create a ParameterizedType instance yourself, say using TypeToken.getParameterized or your custom ParameterizedType implementation
        if ( actualTypeArg0 instanceof ParameterizedType && ((ParameterizedType) actualTypeArg0).getRawType() == Class.class ) {
            return true;
        }
    }
}
return false;

The ad-hoc comments should explain why the previous implementation did not cover all the cases. 临时评论应解释为什么先前的实施未涵盖所有情况。 Even more, you can write a reusable utility method that would recognize the raw class itself. 甚至,您可以编写一个可重用的实用程序方法来识别原始类本身。

private static Type getRawClass(final Type type) {
    if ( type instanceof ParameterizedType ) {
        return ((ParameterizedType) type).getRawType();
    }
    return type;
}

And then those two checks could be collapsed into a single one: 然后可以将这两张支票折叠成一张支票:

if ( getRawClass(actualTypeArg0) == Class.class ) {
    return true;
}

== should work just fine with java.class.Class instances since its instances are effective flyweights, and can improve readability here. ==java.class.Class实例上应该可以正常工作 ,因为它的实例是有效的flyweight,并且可以提高此处的可读性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM