13

I'm using quite a few immutable collections and I'm curious how to deserialize them using Gson. As nobody answered and I've found the solution myself, I'm simplifying the question and presenting my own answer.

I had two problems:

  • How to write a single Deserializer working for all ImmutableList<XXX>?
  • How to register it for all ImmutableList<XXX>?
maaartinus
  • 44,714
  • 32
  • 161
  • 320

3 Answers3

13

Update: There's https://github.com/acebaggins/gson-serializers which covers many guava collections:

  • ImmutableList
  • ImmutableSet
  • ImmutableSortedSet
  • ImmutableMap
  • ImmutableSortedMap

How to write a single Deserializer working for all ImmutableList?

The idea is simple, transform the passed Type representing an ImmutableList<T> into a Type representing List<T>, use the build-in Gson's capability to create a List and convert it to an ImmutableList.

class MyJsonDeserializer implements JsonDeserializer<ImmutableList<?>> {
    @Override
    public ImmutableList<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
        final Type type2 = ParameterizedTypeImpl.make(List.class, ((ParameterizedType) type).getActualTypeArguments(), null);
        final List<?> list = context.deserialize(json, type2);
        return ImmutableList.copyOf(list);
    }
}

There are multiple ParameterizedTypeImpl classes in Java libraries I use, but none of them intended for public usage. I tested it with sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.

How to register it for all ImmutableList?

That part is trivial, the first argument to register is java.lang.reflect.Type which mislead me to using ParameterizedType, where simply using Class does the job:

final Gson gson = new GsonBuilder()
    .registerTypeAdapter(ImmutableList.class, myJsonDeserializer)
    .create();
maaartinus
  • 44,714
  • 32
  • 161
  • 320
  • Can we avoid using of ParameterizedTypeImpl? For example using smth like: final List> list = context.deserialize(json, List.class); – Alexander Bezrodniy Aug 29 '13 at 12:11
  • @Alexander Bezrodniy: I'm afraid, it's not possible. You can avoid it all by using a `TypeAdapter>`, but then you have to write some other boilerplate code: 1. Implement `write` even though the default implementation would suffice, 2. Deal with `null` in `read`. It's all a bit low level stuff. – maaartinus Sep 05 '13 at 23:59
  • I guess now I understand your question. You can't leave out the generics; without them the list content can't be deserialized properly. – maaartinus Sep 06 '13 at 00:00
  • @maaartinus is this not an issue? "ParameterizedTypeImpl is internal proprietary API and may be removed in a future release" – Riaan Schutte Jun 22 '17 at 23:36
  • @Riaan Sure, it may be, but Java is very conservative and if it gets removed, you'll probably find another one. Or better, use one of the other answers. *At the time of my posting, I didn't knew better (maybe it wasn't even possible). Now, I can't fix my answer without duplicating another one.* – maaartinus Jun 23 '17 at 00:34
  • @maaartinus (and everyone else using this solution), please prefer using [`TypeToken.getParameterized`](https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/reflect/TypeToken.html#getParameterized-java.lang.reflect.Type-java.lang.reflect.Type...-) which has been recently added to Gson and is exactly what you are looking for. – Marcono1234 Jun 23 '19 at 21:11
8

One more implementation without ParameterizedTypeImpl

@Override
public ImmutableList<?> deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context) throws JsonParseException {
    @SuppressWarnings("unchecked")
    final TypeToken<ImmutableList<?>> immutableListToken = (TypeToken<ImmutableList<?>>) TypeToken.of(type);
    final TypeToken<? super ImmutableList<?>> listToken = immutableListToken.getSupertype(List.class);
    final List<?> list = context.deserialize(json, listToken.getType());
    return ImmutableList.copyOf(list);
}
komarik
  • 93
  • 1
  • 6
  • Any way this can be updated for latest version of Gson? – lostintranslation Dec 03 '18 at 01:56
  • @lostintranslation, what's wrong with current implementation? I've checked and it works for 2.8.5 version. – komarik Dec 04 '18 at 20:13
  • @komarik, the problem is that the used `TypeToken` is the one from guava. Gson's `TypeToken` does not have a `getSupertype` method (at least in 2.8.5). However, recently the method [`getParameterized`](https://static.javadoc.io/com.google.code.gson/gson/2.8.5/com/google/gson/reflect/TypeToken.html#getParameterized-java.lang.reflect.Type-java.lang.reflect.Type...-) was added which can be used here. – Marcono1234 Jun 23 '19 at 21:09
3

@maaartinus already covered the second question, so I'll post a complementary Guava-based solution to the first question which doesn't require ParametrizedTypeImpl

public final class ImmutableListDeserializer implements JsonDeserializer<ImmutableList<?>> {
  @Override
  public ImmutableList<?> deserialize(final JsonElement json, final Type type,
                                      final JsonDeserializationContext context)
      throws JsonParseException {
    final Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
    final Type parameterizedType = listOf(typeArguments[0]).getType();
    final List<?> list = context.deserialize(json, parameterizedType);
    return ImmutableList.copyOf(list);
  }

  private static <E> TypeToken<List<E>> listOf(final Type arg) {
    return new TypeToken<List<E>>() {}
        .where(new TypeParameter<E>() {}, (TypeToken<E>) TypeToken.of(arg));   
  }
}
Sergei Lebedev
  • 2,659
  • 20
  • 23