Bộ phân chia tùy chỉnh Gson cho một biến (trong số nhiều) trong một đối tượng sử dụng TypeAdapter


96

Tôi đã thấy rất nhiều ví dụ đơn giản về việc sử dụng TypeAdapter tùy chỉnh. Hữu ích nhất đã được Class TypeAdapter<T>. Nhưng điều đó vẫn chưa trả lời câu hỏi của tôi.

Tôi muốn tùy chỉnh tuần tự hóa của một trường trong đối tượng và để cơ chế Gson mặc định xử lý phần còn lại.

Đối với mục đích thảo luận, chúng ta có thể sử dụng định nghĩa lớp này làm lớp của đối tượng mà tôi muốn tuần tự hóa. Tôi muốn để Gson tuần tự hóa hai thành viên lớp đầu tiên cũng như tất cả các thành viên tiếp xúc của lớp cơ sở và tôi muốn thực hiện tuần tự hóa tùy chỉnh cho thành viên lớp thứ 3 và cuối cùng được hiển thị bên dưới.

public class MyClass extends SomeClass {

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

Câu trả lời:


131

Đây là một câu hỏi tuyệt vời bởi vì nó cô lập một cái gì đó đáng ra dễ dàng nhưng thực tế lại đòi hỏi rất nhiều mã.

Để bắt đầu, hãy viết một bản tóm tắt TypeAdapterFactorycung cấp cho bạn các móc để sửa đổi dữ liệu gửi đi. Ví dụ này sử dụng một API mới trong Gson 2.2 được gọi là getDelegateAdapter()cho phép bạn tra cứu bộ điều hợp mà Gson sẽ sử dụng theo mặc định. Bộ điều hợp đại biểu cực kỳ tiện dụng nếu bạn chỉ muốn điều chỉnh hành vi tiêu chuẩn. Và không giống như các bộ điều hợp loại tùy chỉnh đầy đủ, chúng sẽ tự động cập nhật khi bạn thêm và xóa các trường.

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

Lớp trên sử dụng tuần tự hóa mặc định để lấy một cây JSON (được đại diện bởi JsonElement), và sau đó gọi phương thức hook beforeWrite()để cho phép lớp con tùy chỉnh cây đó. Tương tự đối với deserialization với afterRead().

Tiếp theo, chúng tôi phân lớp này cho MyClassví dụ cụ thể . Để minh họa, tôi sẽ thêm một thuộc tính tổng hợp được gọi là 'kích thước' vào bản đồ khi nó được tuần tự hóa. Và để đối xứng, tôi sẽ loại bỏ nó khi nó được khử trên không. Trong thực tế, đây có thể là bất kỳ tùy chỉnh nào.

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

Cuối cùng, kết hợp tất cả lại với nhau bằng cách tạo một phiên bản tùy chỉnh Gsonsử dụng bộ điều hợp loại mới:

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

Mới Gson của TypeAdapterTypeAdapterFactory loại là cực kỳ mạnh mẽ, nhưng chúng cũng trừu tượng và chịu thực hành để sử dụng một cách hiệu quả. Hy vọng rằng bạn thấy ví dụ này hữu ích!


@Jesse Cảm ơn bạn! Tôi sẽ không bao giờ tìm ra điều này nếu không có sự giúp đỡ của bạn!
MountainX

Tôi đã không thể nhanh chóng new MyClassTypeAdapterFactory()với ctor tin ...
MountainX

Ah, xin lỗi về điều đó. Tôi đã làm tất cả những điều này trong một tệp.
Jesse Wilson

7
Mechansim đó (beforeWrite và afterRead) phải là một phần của lõi GSon. Cảm ơn!
Melanie

2
Tôi đang sử dụng TypeAdapter để tránh các vòng lặp vô hạn do tham chiếu lẫn nhau .. đây là một cơ chế tuyệt vời, cảm ơn bạn @Jesse mặc dù tôi muốn hỏi liệu bạn có ý tưởng đạt được hiệu quả tương tự với cơ chế này không .. Tôi có những điều trong đầu nhưng Tôi muốn lắng nghe ý kiến ​​của bạn .. cảm ơn bạn!
Mohammed R. El-Khoudary

16

Có một cách tiếp cận khác cho điều này. Như Jesse Wilson nói, điều này được cho là dễ dàng. Và đoán những gì, nó dễ dàng!

Nếu bạn triển khai JsonSerializerJsonDeserializercho loại của mình, bạn có thể xử lý các phần bạn muốn và ủy quyền cho Gson cho mọi thứ khác , với rất ít mã. Tôi đang trích dẫn câu trả lời của @ Perception cho một câu hỏi khác bên dưới để thuận tiện, hãy xem câu trả lời đó để biết thêm chi tiết:

Trong trường hợp này, tốt hơn hết là sử dụng a JsonSerializertrái ngược với a TypeAdapter, vì lý do đơn giản là trình tuần tự hóa có quyền truy cập vào ngữ cảnh tuần tự hóa của họ.

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

Ưu điểm chính của điều này (ngoài việc tránh các cách giải quyết phức tạp) là bạn vẫn có thể tận dụng các bộ điều hợp loại khác và bộ nối tiếp tùy chỉnh có thể đã được đăng ký trong ngữ cảnh chính. Lưu ý rằng việc đăng ký bộ nối tiếp và bộ điều hợp sử dụng cùng một mã.

Tuy nhiên, tôi sẽ thừa nhận rằng cách tiếp cận của Jesse có vẻ tốt hơn nếu bạn thường xuyên sửa đổi các trường trong đối tượng Java của mình. Đó là sự đánh đổi giữa tính dễ sử dụng và tính linh hoạt, hãy lựa chọn của bạn.


1
Điều này không thể ủy quyền tất cả các trường khác valuecho gson
Wesley

10

Đồng nghiệp của tôi cũng đề cập đến việc sử dụng @JsonAdapterchú thích

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

Trang đã được chuyển đến đây: https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/annotations/JsonAdapter.html

Thí dụ:

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

1
Điều này hoạt động khá tốt cho trường hợp sử dụng của tôi. Cảm ơn rất nhiều.
Neoklosch

1
Dưới đây là một liên kết WebArchive vì bản gốc tại là chết: web.archive.org/web/20180119143212/https://google.github.io/...
Floating Sunfish
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.