Пользовательский серализатор Gson для одной переменной (из многих) в объекте с использованием TypeAdapter

Я видел множество простых примеров использования пользовательского TypeAdapter. Наиболее полезным было Class TypeAdapter<T>. Но это еще не ответило на мой вопрос.

Я хочу настроить сериализацию одного поля в объекте и позволить механизму Gson по умолчанию заботиться обо всем остальном.

В целях обсуждения мы можем использовать это определение класса как класс объекта, который я хочу сериализовать. Я хочу, чтобы Gson сериализовал первые два члена класса, а также всех уязвимых членов базового класса, и я хочу сделать пользовательскую сериализацию для третьего и последнего членов класса, показанных ниже.

public class MyClass extends SomeClass {

@Expose private HashMap<String, MyObject1> lists;
@Expose private HashMap<String, MyObject2> sources;
private LinkedHashMap<String, SomeClass> customSerializeThis;
    [snip]
}

Ответ 1

Это отличный вопрос, потому что он изолирует то, что должно быть легко, но на самом деле требует большого количества кода.

Чтобы начать, напишите реферат TypeAdapterFactory, который дает вам переходы для изменения исходящих данных. В этом примере используется новый API в Gson 2.2 под названием getDelegateAdapter(), который позволяет вам искать адаптер, который Gson будет использовать по умолчанию. Адаптеры делегатов чрезвычайно удобны, если вы просто хотите настроить стандартное поведение. И в отличие от всех адаптеров настраиваемого типа, они будут обновляться автоматически при добавлении и удалении полей.

public abstract class CustomizedTypeAdapterFactory<C>
    implements TypeAdapterFactory {
  private final Class<C> customizedClass;

  public CustomizedTypeAdapterFactory(Class<C> customizedClass) {
    this.customizedClass = customizedClass;
  }

  @SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal
  public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
    return type.getRawType() == customizedClass
        ? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type)
        : null;
  }

  private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) {
    final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type);
    final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
    return new TypeAdapter<C>() {
      @Override public void write(JsonWriter out, C value) throws IOException {
        JsonElement tree = delegate.toJsonTree(value);
        beforeWrite(value, tree);
        elementAdapter.write(out, tree);
      }
      @Override public C read(JsonReader in) throws IOException {
        JsonElement tree = elementAdapter.read(in);
        afterRead(tree);
        return delegate.fromJsonTree(tree);
      }
    };
  }

  /**
   * Override this to muck with {@code toSerialize} before it is written to
   * the outgoing JSON stream.
   */
  protected void beforeWrite(C source, JsonElement toSerialize) {
  }

  /**
   * Override this to muck with {@code deserialized} before it parsed into
   * the application type.
   */
  protected void afterRead(JsonElement deserialized) {
  }
}

Вышеприведенный класс использует сериализацию по умолчанию для получения дерева JSON (представленным JsonElement), а затем вызывает метод hook beforeWrite(), чтобы позволить подклассу настраивать это дерево. Аналогично для десериализации с afterRead().

Далее мы подклассифицируем это для конкретного примера MyClass. Чтобы проиллюстрировать, я добавлю синтетическое свойство "размер" к карте при ее сериализации. И для симметрии я удалю его, когда он будет десериализован. На практике это может быть любая настройка.

private class MyClassTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyClass> {
  private MyClassTypeAdapterFactory() {
    super(MyClass.class);
  }

  @Override protected void beforeWrite(MyClass source, JsonElement toSerialize) {
    JsonObject custom = toSerialize.getAsJsonObject().get("custom").getAsJsonObject();
    custom.add("size", new JsonPrimitive(custom.entrySet().size()));
  }

  @Override protected void afterRead(JsonElement deserialized) {
    JsonObject custom = deserialized.getAsJsonObject().get("custom").getAsJsonObject();
    custom.remove("size");
  }
}

Наконец, соединим все это, создав индивидуальный экземпляр Gson, который использует новый адаптер типа:

Gson gson = new GsonBuilder()
    .registerTypeAdapterFactory(new MyClassTypeAdapterFactory())
    .create();

Gson new TypeAdapter и TypeAdapterFactory типы чрезвычайно эффективны, но они также абстрактны и используют практику для эффективного использования. Надеемся, вы найдете этот пример полезным!

Ответ 2

Там другой подход к этому. Как говорит Джесси Уилсон, это должно быть легко. И угадайте, что это легко!

Если вы реализуете JsonSerializer и JsonDeserializer для своего типа, вы можете обрабатывать нужные вам части и делегировать Gson для всего остального, с очень маленьким кодом. Я цитирую из @Персивный ответ по другому вопросу ниже для удобства, см. Этот ответ для более подробной информации:

В этом случае лучше использовать JsonSerializer в отличие от TypeAdapter, по той простой причине, что сериализаторы имеют доступ к их контексту сериализации.

public class PairSerializer implements JsonSerializer<Pair> {
    @Override
    public JsonElement serialize(final Pair value, final Type type,
            final JsonSerializationContext context) {
        final JsonObject jsonObj = new JsonObject();
        jsonObj.add("first", context.serialize(value.getFirst()));
        jsonObj.add("second", context.serialize(value.getSecond()));
        return jsonObj;
    }
}

Основное преимущество этого (помимо избежания сложных обходных решений) заключается в том, что вы можете использовать преимущества других адаптеров типов и настраиваемых сериализаторов, которые могли быть зарегистрированы в основном контексте. Обратите внимание, что для регистрации сериализаторов и адаптеров используется тот же самый код.

Однако я буду признавать, что подход Джесси выглядит лучше, если вы часто меняете поля в своем Java-объекте. Это компромисс от простоты использования и гибкости, сделайте выбор.