96

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

オブジェクト内の単一フィールドのシリアル化をカスタマイズし、デフォルトの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]
}
4

3 に答える 3

134

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

まず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型は非常に強力ですが、抽象的でもあり、効果的に使用するには練習が必要です。この例がお役に立てば幸いです。

于 2012-06-30T07:50:50.967 に答える
16

これには別のアプローチがあります。Jesse Wilson が言うように、これは簡単なはずです。そして、それ簡単です!

型にJsonSerializerandを実装すると、必要な部分を処理し、それ以外のすべてを Gson に委譲することができます。わずかなコードで済みます。便宜上、以下の別の質問に対する@Perceptionの回答から引用しています。詳細については、その回答を参照してください。JsonDeserializer

この場合、シリアライザーがシリアライゼーション コンテキストにアクセスできるという単純な理由JsonSerializerから、 a ではなくa を使用することをお勧めします。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 のアプローチの方が適切に見えることは認めます。使いやすさと柔軟性のトレードオフです。お好きな方をお選びください。

于 2016-02-02T07:57:53.220 に答える