web-dev-qa-db-ja.com

TypeAdapterを使用してオブジェクト内の1つの変数(多数)のGsonカスタムセラライザー

カスタムTypeAdapterを使用した簡単な例をたくさん見ました。最も役立つのは Class TypeAdapter<T> 。しかし、それはまだ私の質問に答えていません。

オブジェクト内の1つのフィールドのシリアル化をカスタマイズし、デフォルトのGsonメカニズムが残りを処理するようにします。

議論のために、このクラス定義を、シリアル化するオブジェクトのクラスとして使用できます。 Gsonに最初の2つのクラスメンバーと基本クラスのすべての公開されたメンバーをシリアル化させ、次に示す3番目と最後のクラスメンバーのカスタムシリアル化を行います。

public class MyClass extends SomeClass {

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

これは簡単なはずですが、実際には多くのコードを必要とするものを分離するため、大きな質問です。

まず、発信データを変更するためのフックを提供する抽象TypeAdapterFactoryを記述します。この例では、Gson 2.2のgetDelegateAdapter()という新しいAPIを使用します。これにより、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();
_

Gsonの新しい TypeAdapter および TypeAdapterFactory タイプは非常に強力ですが、それらは抽象的であり、効果的に使用するための実践も必要です。この例をお役立てください。

124
Jesse Wilson

これには別のアプローチがあります。ジェシー・ウィルソンが言うように、これは簡単なはずです。そして、それは何だと思いますかは簡単です

タイプにJsonSerializerおよびJsonDeserializerを実装する場合、必要な部分を処理し、他のすべてについてGsonに委任できます、非常に少ないコードで。 @ Perceptionの別の質問に対する回答 から引用しています。便宜上、以下の回答を参照してください。

この場合、 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オブジェクト内のフィールドを頻繁に変更する場合は、Jesseのアプローチの方が見た目が良いことを認識します。使いやすさと柔軟性のトレードオフです。 。

12
Vicky Chijwani

私の同僚は、@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;
   }
 }
6
dazza5000