Gson seralizer ที่กำหนดเองสำหรับตัวแปรเดียว (จากหลายตัว) ในวัตถุโดยใช้ TypeAdapter


96

ฉันได้เห็นตัวอย่างง่ายๆมากมายเกี่ยวกับการใช้ TypeAdapter ที่กำหนดเอง Class TypeAdapter<T>เป็นประโยชน์มากที่สุดได้รับ แต่นั่นยังไม่ตอบคำถามของฉัน

ฉันต้องการปรับแต่งการทำให้เป็นอนุกรมของฟิลด์เดียวในออบเจ็กต์และปล่อยให้กลไก Gson เริ่มต้นดูแลส่วนที่เหลือ

เพื่อจุดประสงค์ในการสนทนาเราสามารถใช้นิยามคลาสนี้เป็นคลาสของอ็อบเจ็กต์ที่ฉันต้องการทำให้เป็นอนุกรม ฉันต้องการให้ 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ที่ช่วยให้คุณสามารถแก้ไขข้อมูลที่ส่งออกได้ ตัวอย่างนี้ใช้ 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) จากนั้นเรียกใช้เมธอด hook beforeWrite()เพื่อให้คลาสย่อยปรับแต่งทรีนั้น ในทำนองเดียวกันสำหรับ deserialization กับafterRead().

ต่อไปเราจะย่อยคลาสนี้สำหรับMyClassตัวอย่างเฉพาะ เพื่อเป็นตัวอย่างฉันจะเพิ่มคุณสมบัติสังเคราะห์ที่เรียกว่า 'size' ลงในแผนที่เมื่อมันต่อเนื่องกัน และสำหรับความสมมาตรฉันจะลบออกเมื่อมันถูก deserialized ในทางปฏิบัตินี่อาจเป็นการปรับแต่งใด ๆ

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ประเภทที่มีประสิทธิภาพมาก แต่พวกเขายังนามธรรมและใช้การปฏิบัติที่จะใช้อย่างมีประสิทธิภาพ หวังว่าตัวอย่างนี้จะเป็นประโยชน์!


@Jesse ขอบคุณ! ฉันจะไม่คิดออกโดยปราศจากความช่วยเหลือของคุณ!
MountainX

ฉันไม่สามารถสร้างอินสแตนซ์new MyClassTypeAdapterFactory()กับ ctor ส่วนตัวได้ ...
MountainX

อาขอโทษเกี่ยวกับเรื่องนั้น ฉันทำทั้งหมดนี้ในไฟล์เดียว
Jesse Wilson

7
กลไกนั้น (beforeWrite และ afterRead) ควรเป็นส่วนหนึ่งของแกน GSon ขอบคุณ!
Melanie

2
ฉันใช้ TypeAdapter เพื่อหลีกเลี่ยงการวนซ้ำที่ไม่มีที่สิ้นสุดเนื่องจากการอ้างอิงร่วมกัน .. นี่เป็นกลไกที่ยอดเยี่ยมขอบคุณ @Jesse แม้ว่าฉันอยากจะถามว่าคุณมีความคิดที่จะบรรลุผลเช่นเดียวกันกับกลไกนี้หรือไม่ .. ฉันมีสิ่งต่างๆอยู่ในใจ แต่ ฉันต้องการฟังความคิดเห็นของคุณ .. ขอบคุณ!
Mohammed R.El-Khoudary

16

มีวิธีอื่นในการนี้ อย่างที่เจสซี่วิลสันพูดนี่น่าจะง่าย และคาดเดาสิ่งที่มันเป็นเรื่องง่าย!

หากคุณใช้งานJsonSerializerและJsonDeserializerสำหรับประเภทของคุณคุณสามารถจัดการกับส่วนที่คุณต้องการและมอบหมายให้ Gson สำหรับทุกอย่างได้โดยใช้รหัสเพียงเล็กน้อย ฉันอ้างจากคำตอบของ @ Perception สำหรับคำถามอื่นด้านล่างเพื่อความสะดวกดูคำตอบนั้นสำหรับรายละเอียดเพิ่มเติม:

ในกรณีนี้ควรใช้ a JsonSerializerซึ่งตรงข้ามกับ a TypeAdapterด้วยเหตุผลง่ายๆที่ serializers สามารถเข้าถึงบริบทการทำให้เป็นอนุกรมได้

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 ของคุณ เป็นการแลกเปลี่ยนความสะดวกในการใช้งานเทียบกับความยืดหยุ่นเลือกของคุณ


1
สิ่งนี้ล้มเหลวในการมอบหมายฟิลด์อื่น ๆ ทั้งหมดvalueให้กับ gson
wesley

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

1
นี่คือลิงค์ WebArchive เพราะตอนนี้ต้นฉบับหมดแล้ว: web.archive.org/web/20180119143212/https://google.github.io/…
Floating Sunfish
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.