TypeAdapter를 사용하는 개체의 하나의 변수 (여러 개 중)에 대한 Gson 사용자 지정 seralizer


96

사용자 지정 TypeAdapter를 사용하는 간단한 예제를 많이 보았습니다. 가장 도움이되는 것은 Class TypeAdapter<T>. 그러나 그것은 아직 내 질문에 대한 답을 얻지 못했습니다.

객체에서 단일 필드의 직렬화를 사용자 정의하고 기본 Gson 메커니즘이 나머지를 처리하도록하고 싶습니다.

토론 목적으로이 클래스 정의를 직렬화하려는 객체의 클래스로 사용할 수 있습니다. Gson이 처음 두 클래스 멤버와 기본 클래스의 모든 노출 된 멤버를 직렬화하도록하고, 아래 표시된 세 번째 및 마지막 클래스 멤버에 대해 사용자 지정 직렬화를 수행하고 싶습니다.

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나가는 데이터를 수정할 수있는 후크를 제공 하는 초록 을 작성하십시오 . 이 예에서는 getDelegateAdapter()Gson이 기본적으로 사용할 어댑터를 조회 할 수 있도록하는 Gson 2.2의 새 API를 사용합니다. 델리게이트 어댑터는 표준 동작을 조정하려는 경우 매우 편리합니다. 또한 전체 사용자 지정 유형 어댑터와 달리 필드를 추가 및 제거 할 때 자동으로 최신 상태로 유지됩니다.

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()를 호출 하여 하위 클래스가 해당 트리를 사용자 지정할 수 있도록합니다. .NET을 사용한 역 직렬화와 유사합니다 afterRead().

다음으로 특정 MyClass예제를 위해 이것을 하위 클래스로 만듭니다. 설명을 위해 맵이 직렬화 될 때 'size'라는 합성 속성을 맵에 추가합니다. 대칭을 위해 역 직렬화 될 때 제거합니다. 실제로 이것은 모든 사용자 정의가 될 수 있습니다.

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의 새로운 TypeAdapterTypeAdapterFactory 유형은 매우 강력하지만 추상적이고 효과적으로 사용하기 위해 연습을합니다. 이 예제가 유용하기를 바랍니다.


@Jesse 감사합니다! 나는 당신의 도움 없이는 이것을 결코 알지 못했을 것입니다!
MountainX

나는 인스턴스화 할 수 없습니다 new MyClassTypeAdapterFactory()... 민간의 ctor와
MountainX

아, 죄송합니다. 이 모든 것을 하나의 파일로 수행했습니다.
Jesse Wilson

7
그 mechansim (beforeWrite 및 afterRead)은 GSon 코어의 일부 여야합니다. 감사!
Melanie

2
상호 참조로 인한 무한 루프를 피하기 위해 TypeAdapter를 사용하고 있습니다.이 메커니즘을 사용하여 동일한 효과를 얻을 수 있는지 물어보고 싶지만 @Jesse에게 감사합니다. 여러분의 의견을 듣고 싶습니다. 감사합니다!
Mohammed R. El-Khoudary

16

이것에 대한 또 다른 접근 방식이 있습니다. Jesse Wilson이 말했듯이 이것은 쉬운 일입니다. 그리고 그것은 무엇을 생각 입니다 쉽게!

당신이 구현 JsonSerializer하고 JsonDeserializer당신의 유형에 대해, 당신은 아주 적은 코드로 당신이 원하는 부분을 처리하고 다른 모든 것을 Gson에 위임 할 수 있습니다 . 편의를 위해 아래 다른 질문에 대한 @Perception의 답변을 인용하고 있습니다 . 자세한 내용은 해당 답변을 참조하십시오.

이 경우 serializer가 직렬화 컨텍스트에 액세스 할 수있는 간단한 이유 때문에 JsonSerializera 대신 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;
    }
}

이것의 주요 이점 (복잡한 해결 방법을 피하는 것 외에)은 기본 컨텍스트에 등록되었을 수있는 다른 유형 어댑터 및 사용자 지정 serializer를 계속 활용할 수 있다는 것입니다. serializer 및 어댑터 등록은 정확히 동일한 코드를 사용합니다.

그러나 Java 객체의 필드를 자주 수정하는 경우 Jesse의 접근 방식이 더 좋아 보인다는 것을 인정합니다. 사용 용이성과 유연성의 절충안입니다. 선택하십시오.


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/...
피쉬 부동
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.