TypeAdapter kullanan bir nesnede bir değişken (çok sayıda) için Gson özel seralizer


96

Özel bir TypeAdapter kullanmanın birçok basit örneğini gördüm. En yararlı olanı oldu Class TypeAdapter<T>. Ama bu henüz soruma cevap vermedi.

Nesnedeki tek bir alanın serileştirmesini özelleştirmek ve geri kalanını varsayılan Gson mekanizmasının halletmesine izin vermek istiyorum.

Tartışma amacıyla, bu sınıf tanımını serileştirmek istediğim nesnenin sınıfı olarak kullanabiliriz. Gson'un ilk iki sınıf üyesini ve aynı zamanda temel sınıfın tüm maruz kalan üyelerini serileştirmesine izin vermek istiyorum ve aşağıda gösterilen 3. ve son sınıf üyesi için özel serileştirme yapmak istiyorum.

public class MyClass extends SomeClass {

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

Yanıtlar:


131

Bu harika bir soru çünkü kolay olması gereken ama aslında çok fazla kod gerektiren bir şeyi ayırıyor.

Başlamak için TypeAdapterFactory, giden verileri değiştirmek için size kancalar sağlayan bir özet yazın . Bu örnek, Gson 2.2'de getDelegateAdapter(), Gson'un varsayılan olarak kullanacağı bağdaştırıcıya bakmanıza olanak tanıyan yeni bir API kullanır . Temsilci bağdaştırıcıları, yalnızca standart davranışı değiştirmek istiyorsanız son derece kullanışlıdır. Ve tam özel tip adaptörlerin aksine, siz alanları ekleyip çıkardıkça otomatik olarak güncel kalırlar.

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) {
  }
}

Yukarıdaki sınıf, bir JSON ağacı (ile temsil edilir JsonElement) almak için varsayılan serileştirmeyi kullanır ve ardından beforeWrite()alt sınıfın bu ağacı özelleştirmesine izin vermek için kanca yöntemini çağırır . Benzer şekilde seriyi kaldırma için afterRead().

Daha sonra, belirli bir MyClassörnek için bunu alt sınıflara ayırıyoruz . Göstermek için, serileştirildiğinde haritaya 'boyut' adlı sentetik bir özellik ekleyeceğim. Ve simetri için seri halden çıkarıldığında onu kaldıracağım. Pratikte bu herhangi bir özelleştirme olabilir.

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

Son olarak Gson, yeni tip bağdaştırıcıyı kullanan özelleştirilmiş bir örnek oluşturarak hepsini bir araya getirin :

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

Gson'un yeni TypeAdapter ve TypeAdapterFactory türleri son derece güçlüdür, ancak aynı zamanda soyuttur ve etkili bir şekilde kullanmak için pratik gerektirir. Umarım bu örneği faydalı bulursunuz!


@Jesse Teşekkürler! Senin yardımın olmadan bunu asla çözemezdim!
MountainX

new MyClassTypeAdapterFactory()Özel ctor ile somutlaştıramadım ...
MountainX

Ah, bunun için üzgünüm. Bunların hepsini tek bir dosyada yaptım.
Jesse Wilson

7
Bu mechansim (beforeWrite ve afterRead) GSon çekirdeğinin bir parçası olmalıdır. Teşekkürler!
Melanie

2
Karşılıklı referanslama nedeniyle sonsuz döngülerden kaçınmak için TypeAdapter kullanıyorum .. bu harika bir mekanizmadır @Jesse, yine de bu mekanizma ile aynı etkiyi elde etme fikriniz olup olmadığını sormak istiyorum .. Aklımda bir şeyler var ama Fikrinizi dinlemek istiyorum .. teşekkür ederim!
Mohammed R. El-Khoudary

16

Buna başka bir yaklaşım var. Jesse Wilson'ın dediği gibi, bunun kolay olması gerekiyor. Ve tahmin et ne oldu, çok kolay!

Eğer uygularsanız JsonSerializerve türünüz için , çok az kodla JsonDeserializeristediğiniz parçaları halledebilir ve diğer her şey için Gson'a delege edebilirsiniz . Kolaylık sağlamak için aşağıdaki başka bir soruda @ Perception'ın cevabından alıntı yapıyorum , daha fazla ayrıntı için bu yanıta bakın:

Bu durumda, serileştiricilerin kendi serileştirme bağlamlarına erişebilmelerinin basit bir nedeni JsonSerializerolarak, a yerine a kullanmak daha iyidir 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;
    }
}

Bunun temel avantajı (karmaşık geçici çözümlerden kaçınmanın dışında), ana bağlamda kaydedilmiş olabilecek diğer tür bağdaştırıcılardan ve özel serileştiricilerden yine de yararlanabilmenizdir. Serileştiricilerin ve adaptörlerin kayıtlarının tam olarak aynı kodu kullandığını unutmayın.

Bununla birlikte, Java nesnenizdeki alanları sık sık değiştirecekseniz Jesse'nin yaklaşımının daha iyi göründüğünü kabul edeceğim. Bu, kullanım kolaylığı ile esneklik arasında bir değiş tokuş, seçiminizi yapın.


1
Bu, diğer tüm alanları valuegson'a devredemez
wesley

10

Meslektaşım da @JsonAdapterek açıklamanın kullanımından bahsetti

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

Sayfa buraya taşındı: https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/JsonAdapter.html

Misal:

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

1
Bu benim kullanım durumum için oldukça iyi çalışıyor. Çok teşekkürler.
Neoklosch

1
İşte bir WebArchive bağlantısı, çünkü orijinal artık öldü: web.archive.org/web/20180119143212/https://google.github.io/…
Floating Sunfish
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.