[英]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]}
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.