Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simpler use of TypeAdapterFactory

Tags:

java

gson

adapter

AFAIK the most flexible gson customization is possible via TypeAdapterFactory, however it may get needlessly complicated. It forces me to write for each handled class both read and write, while sometimes only one method is really needed. Moreover, sometimes a JsonSerializer and/or JsonDeserializer were much easier to write, e.g. like here. This leads me to these questions:

  • Is it possible to write a TypeAdapter which simply delegates one of its methods (e.g. writing of ImmutableList to writing of List)?
  • Is it possible to somehow use JsonSerializer and/or JsonDeserializer together with the TypeAdapterFactory? Alternatively, is there a factory for them?
like image 596
maaartinus Avatar asked Sep 25 '12 01:09

maaartinus


1 Answers

It is possible to create a TypeAdapter that delegates one of its methods. This use case is an important part of the API, and there's a getDelegateAdapter() method for just this purpose. Pass this as the first argument to getDelegateAdapter which will return the adapter that takes precedence after the current factory.

TypeAdapterFactory immutableListFactory = new TypeAdapterFactory() {
  @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    if (!(type.getType() instanceof ParameterizedType)
        || !type.getRawType().equals(ImmutableList.class)) {
      return null;
    }

    ParameterizedType parameterizedType = (ParameterizedType) type.getType();
    TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
    TypeAdapter<?> elementAdapter = gson.getAdapter(
        TypeToken.get(parameterizedType.getActualTypeArguments()[0]));
    return new ImmutableListAdapter(delegate, elementAdapter);
  }

  class ImmutableListAdapter<E> extends TypeAdapter<ImmutableList<E>> {
    private TypeAdapter<List<E>> delegate;
    private TypeAdapter<E> element;

    ImmutableListAdapter(TypeAdapter<List<E>> delegate, TypeAdapter<E> element) {
      this.delegate = delegate;
      this.element = element;
    }

    @Override public void write(JsonWriter out, ImmutableList<E> value) throws IOException {
      delegate.write(out, value);
    }

    @Override public ImmutableList<E> read(JsonReader in) throws IOException {
      if (in.peek() == JsonToken.NULL) {
        in.nextNull();
        return null;
      }
      ImmutableList.Builder<E> builder = ImmutableList.builder();
      in.beginArray();
      while (in.hasNext()) {
        builder.add(element.read(in));
      }
      in.endArray();
      return builder.build();
    }
  }
};

You can mix and match JsonSerializer/JsonDeserializer with TypeAdapterFactory, but not directly. The simplest way is to call back into Gson to serialize child values in your class. In this example we'd change the inner loop to this:

      while (in.hasNext()) {
        builder.add(gson.<E>fromJson(in, elementType));
      }

The main difference between JsonSerializer/JsonDeserializer and TypeAdapter is how many stages it takes to go from JSON to your object model. With JsonSerializer/JsonDeserializer objects are first converted to Gson's DOM model (JsonElement etc.) and then converted into your object model. With TypeAdapter, the intermediate step is skipped.

This makes the type adapter code a little trickier to read and write, so you should prefer it only for optimized code.

like image 136
Jesse Wilson Avatar answered Nov 15 '22 17:11

Jesse Wilson