Вопрос по gson, java, serialization – Пользовательский Seralizer Gson для одной переменной (из нескольких) в объекте, используя TypeAdapter

83

Я видел множество простых примеров использования пользовательского 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]
}

Ваш Ответ

3   ответа
6

Мой коллега также упомянул об использовании@JsonAdapter аннотирование

https://google.github.io/gson/apidocs/com/google/gson/annotations/JsonAdapter.html

Пример:

 private static final class Gadget {
   @JsonAdapter(UserJsonAdapter2.class)
   final User user;
   Gadget(User user) {
       this.user = user;
   }
 }
Это хорошо работает для моего варианта использования. Большое спасибо.
10

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

Если вы реализуетеJsonSerializer а такжеJsonDeserializer для вашего типа вы можете обрабатывать детали, которые вы хотите иdelegate to Gson for everything elseс очень небольшим кодом. Я цитирую@ Восприятие ответ на другой вопрос ниже для удобства смотрите этот ответ для более подробной информации:

In this case its better to use a JsonSerializer as opposed to a TypeAdapter, for the simple reason that serializers have access to their serialization context.

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;
    }
}

The main advantage of this (apart from avoiding complicated workarounds) is that you can still advantage of other type adaptors and custom serializers that might have been registered in the main context. Note that registration of serializers and adapters use the exact same code.

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

119

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

Для начала напишите реферат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), а затем вызывает метод ловушки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();

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

Я не смог создать экземплярnew MyClassTypeAdapterFactory() с частным ctor ... MountainX
@ Джесси Спасибо! Я бы никогда не понял это без вашей помощи! MountainX
Этот механизм (beforeWrite и afterRead) должен быть частью ядра GSon. Спасибо!
+1 также к вашему сведению, @MountainX, Джесси - один из разработчиков gson :)
Я использую TypeAdapter, чтобы избежать бесконечных циклов из-за взаимных ссылок ... это отличный механизм, спасибо @Jesse, хотя я хотел бы спросить, есть ли у вас идея достичь того же эффекта с помощью этого механизма ... Я имею в виду, но Я хочу выслушать ваше мнение .. спасибо!

Похожие вопросы