Gson адаптер для разнородной мультикарты
У меня есть Multimap<Class<?>, Object>
заселены как
multimap.put(o.getClass, o)
каждый объект помещается в надлежащее ведро в соответствии с его классом. Мне нужно сериализовать и десериализовать мультикарту с помощью Gson. Все объекты принадлежат простым классам без параметров типа. Я имею в виду, что каждый из них может быть десериализован с помощью gson.fromJson(json, someClass)
; нет TypeToken
нужен здесь.
Если это поможет, я мог бы использовать TypeToken
или что-то в качестве ключа; Мне все равно Все используемые классы являются подклассами моего класса, если это помогает. Я не хочу разбивать мультикарту на несколько однородных списков, так как их будет десятки. Как это на самом деле ImmutableMultimap
это означало бы еще много строк, которых я хочу избежать.
Что я пробовал: не стоит упоминать. Ни один из адаптеров, которые я написал или увидел, не делает ничего подобного.
1 ответ
Если я вас правильно понимаю, вы можете сделать такой тип адаптера относительно легко.
Во-первых, давайте создадим Multimap
Тип адаптера. Следующие 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();
}
}
Теперь вы можете создать простой Class
ключевой конвертер: конвертер довольно прост и информативен. Более сложные стратегии конвертации можно найти, например, здесь и здесь (последняя не поддерживает массивы полностью).
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 ";
}
И теперь вы можете создать фабрику адаптеров типов, которая может обрабатывать такую мультикарту:
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;
}
}
Наконец, вы можете проверить это:
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");
}
}
Выход:
{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]}
Обновление 1
Хорошо, давайте приступим к созданию isClassKeyMultimap
метод немного умнее.
Я хотел бы работать с
Multimap<Class<Something>, Something>
, тоже.
Вы, наверное, говорите о TypeToken
литералы. Да, я использовал TypeToken.getParameterized(...)
забыв, что Class
Экземпляр также может быть параметризован. Все, что вам нужно сделать, чтобы сделать его умнее, это просто добавить дополнительную проверку метода.
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;
Специальные комментарии должны объяснить, почему предыдущая реализация не охватывала все случаи. Более того, вы можете написать повторно используемый служебный метод, который распознает сам необработанный класс.
private static Type getRawClass(final Type type) {
if ( type instanceof ParameterizedType ) {
return ((ParameterizedType) type).getRawType();
}
return type;
}
И тогда эти две проверки могут быть объединены в одну:
if ( getRawClass(actualTypeArg0) == Class.class ) {
return true;
}
==
должно нормально работать с java.class.Class
экземпляры, поскольку его экземпляры являются эффективными весами и могут улучшить читабельность здесь.