AutoValue 및 다형성을 사용하는 Gson

최근에 RESTful API가 객체 목록을 제공하는 상황에 봉착했습니다. 일반적으로 REST는 변환하기 쉬운 구조를 제공하므로 문제가 되지 않습니다. catch는 다른 객체 내부의 일부 객체가 다형성이고 특정 필드를 구문 분석해야 한다는 것입니다.

이전 솔루션은 제공된 모든 필드를 포함하는 HashMap을 갖는 것이었습니다. 그런 다음 애플리케이션은 반환된 객체의 종류를 확인하고 필요한 모든 키-값 쌍이 사용 가능한 데이터로 채워졌는지 결정해야 했습니다. 이것은 선호되는 솔루션이 아니며 오류가 발생하기 쉽습니다.

사용자 지정 (역)직렬 변환기를 살펴보기 위해 많은 제안이 이루어졌는데, 이는 좋은 생각이지만 많은 작업이 필요합니다. 나는 Gson 저장소에서 "hidden"RuntimeTypeAdapterFactory 클래스를 발견하여 대부분의 문제를 아주 훌륭하게 해결했습니다.


예제 문제



예를 들어 다음 JSON 구조를 사용하십시오.

{
  "article": {
    "title": "Hello world",
    "body": [{
        "type": "text",
        "text": "Hi there"
      }, {
        "type": "image",
        "source": "https://example.com/hello-world-banner.jpg",
        "author": "John Doe",
        "description": "Hello World! banner"
      }, {
        "type": "video",
        "source": "http://mirrorblender.top-ix.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_surround.avi",
        "thumbnail": "https://peach.blender.org/wp-content/uploads/dl_1080p.jpg",
        "title": "Big Buck Bunny",
        "duration": 574
      }
    ]
  }
}


보시다시피 body 배열의 개체에는 반환되는 개체의 유형을 결정하는 type 필드가 있습니다. 이렇게 하면 개체를 고유한 존경하는 모델로 구문 분석할 수 있는 유연성을 얻을 수 있습니다. 이 경우에는 "text ", "image "및 "video "여야 합니다. Google의 AutoValue를 사용하여 아래의 3가지 모델 예를 참조하십시오.

public interface Block {
}

@AutoValue
public abstract class TextBlock implements Block {
  @SerializedName("text")
  public abstract String text();
}

@AutoValue
public abstract class ImageBlock implements Block {
  @SerializedName("source")
  public abstract String source();

  @Nullable
  @SerializedName("author")
  public abstract String author();

  @Nullable
  @SerializedName("description")
  public abstract String description();
}

@AutoValue
public abstract class VideoBlock implements Block {
  @SerializedName("source")
  public abstract String source();

  @SerializedName("thumbnail")
  public abstract String thumbnail();

  @SerializedName("title")
  public abstract String title();

  @SerializedName("duration")
  public abstract int duration();
}


(간결함을 위해 위의 예에서 빌더 및 Gson 유형 어댑터 생성기를 생략했습니다.)


해결책



먼저 Article 모델 자체를 만들어야 합니다. 아래 예에서 List<Block> 반환 유형을 확인할 수 있습니다. Blocks 예제 코드를 살펴보면 모두 Block 인터페이스를 구현하는 것을 볼 수 있습니다. 이렇게 하면 향후 업데이트에서 지원하고자 하는 새로운 유형의 객체를 쉽게 정의할 수 있습니다.

@AutoValue
public abstract class Article {
  @SerializedName("title")
  public abstract String title();

  @SerializedName("blocks")
  public abstract List<Block> blocks();
}


그러나 지금 그것을 구문 분석합니다. 여기에 RuntimeTypeAdapterFactory라는 마법이 개입합니다! Gson 파서를 생성할 때 모든 종류의 어댑터 팩토리를 등록할 수 있습니다. 생성된 AutoValue 어댑터와 런타임 어댑터를 추가하면 설정이 완료됩니다.

예를 들어:

RuntimeTypeAdapterFactory<Block> articleBlockFactory = RuntimeTypeAdapterFactory.of(Block.class, "type")
  .registerSubtype(TextBlock.class, "text")
  .registerSubtype(ImageBlock.class, "image")
  .registerSubtype(VideoBlock.class, "video");

Gson gson = new GsonBuilder()
  .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
  .registerTypeAdapterFactory(AutoValueTypeAdapterFactory.create())
  .registerTypeAdapterFactory(articleBlockFactory)
  .create();

Retrofit retrofit = new Retrofit.Builder()
  .baseUrl("https://api.example.com/")
  .addConverterFactory(GsonConverterFactory.create(gson))
  .build();


Gson 구문 분석기가 유형Block의 개체를 만날 때마다 어댑터는 정의된 하위 유형으로 구문 분석할 수 있는지 확인합니다. API가 "type"와 다른 필드에 유형을 정의하는 경우 이를 제공할 수 있습니다. "type"는 키의 기본 이름이므로 API가 이 방식으로 작동하는 경우 완전히 생략할 수 있습니다.


주의 사항



나는 내 목적을 위해 해결해야 할 몇 가지 사소한 문제에 부딪쳤다. 이러한 결과는 RuntimeTypeAdapterFactory 작업을 시작할 때를 알 때 유용할 수도 있습니다.
  • 파서에서 예기치 않은 유형이 발생하면 예외가 발생하고 전체 개체가 무효화됩니다. — 이 접근 방식이 마음에 들지 않으면 대신 반환하도록 파서를 수정할 수 있습니다null
  • .
  • 구문 분석된"type" 필드는 모델에 반환되지 않습니다. — 이 정보를 원하면 다시 추가해야 합니다
  • .

    즐거운 코딩!

    좋은 웹페이지 즐겨찾기