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


96

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

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

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

public class MyClass extends SomeClass {

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

回答:


131

これは簡単なはずですが、実際には多くのコードが必要なものを分離するので、すばらしい質問です。

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

Gsonの新しいTypeAdapterおよびTypeAdapterFactory型は非常に強力ですが、これらは抽象的であり、効果的に使用するには練習が必要です。この例がお役に立てば幸いです。


@ジェシーありがとう!あなたの助けがなければこれを理解することはできなかったでしょう。
MountainX

私はnew MyClassTypeAdapterFactory()プライベートトラクターでインスタンス化できませんでした...
MountainX

ああ、ごめんなさい。これらすべてを1つのファイルで行いました。
ジェシーウィルソン

7
そのメカニズム(beforeWriteおよびafterRead)はGSonコアの一部である必要があります。ありがとう!
メラニー

2
TypeAdapterを使用して、相互参照による無限ループを回避しています。これは、@ Jesseに感謝する素晴らしいメカニズムです。このメカニズムで同じ効果を達成する考えがあるかどうか尋ねたいと思います。私はあなたの意見を聞きたいです。ありがとうございます!
Mohammed R. El-Khoudary 2014

16

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

自分のタイプに合わせて実装するJsonSerializerJsonDeserializer、必要な部分を処理し、他のすべてのことを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のアプローチの方がよく見えることを認めます。使いやすさと柔軟性のトレードオフです。


1
これは他のすべてのフィールドvalueをgson に委任することに失敗します
ウェズリー

10

同僚も@JsonAdapter注釈の使用について言及しました

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

ページはここに移動しました:https : //www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/JsonAdapter.html

例:

 private static final class Gadget {
   @JsonAdapter(UserJsonAdapter2.class)
   final User user;
   Gadget(User user) {
       this.user = user;
   }
 }

1
これは私のユースケースではかなりうまくいきます。どうもありがとう。
Neoklosch 2018

1
オリジナルは今死んでいるので、ここではWebArchiveリンクです:web.archive.org/web/20180119143212/https://google.github.io/...
マンボウフローティング
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.