Moshi의 data class상속 관계 처리시 유의점

Jinho Shin·2021년 9월 26일
0

안드로이드

목록 보기
4/4

Kotlin을 포함한 다른 언어에서 model(Kotlin에서는 data class)를 사용하다 보면 몇몇 필드들이 중복되는 경우가 있으실거고 중복된 필드들을 각각의 model 클래스에서 사용하다 보면 귀찮은 경우가 있으실 겁니다.

이런 경우에는 보통은 아래와 같이 처리를 하곤 합니다.

  open class Parent(var comp1: String? = null, 
                    var comp2: String? = null
   )
  
  data class Child(var comp3: String, var comp4: String): Parent()

당연히 위처럼 중복되는 필드가 있는 경우에는 위처럼 해결하는 경우가 있지만 Moshi의 경우는 위 예시처럼 작성을 하면 부모 클래스의 필드들이 제대로 파싱이 변환되지 않는 문제가 생깁니다.

첫 프로젝트에서는 문제가 없었는데 현재 진행하는 프로젝트에서 이슈를 발견하였습니다. 첫 프로젝트와 두번째 프로젝트의 차이점은 다음과 같습니다.

//첫 프로젝트
open class Parent(

    @Json(name="comp1")
    var comp1: String? = null,
    
    @Json(name="comp2")
    var comp2: String? = null
)

data class Child(

    @Json(name="comp3")
    var comp3: String? = null,
    
    @Json(name="comp3")
    var comp3: String? = null
)


//두번째 프로젝트
open class Parent(

    @Json(name="fruit")
    var comp1: String? = null,
    
    @Json(name="name")
    var comp2: String? = null
)

data class Child(

    @Json(name="comp3")
    var comp3: String? = null,
    
    @Json(name="comp3")
    var comp3: String? = null
)

첫 프로젝트와 현재 진행하는 프로젝트의 data model의 차이점은 위 Code Snippet에서 보시듯이 @Json 어노테이션 안에 있는 이름의 차이입니다.

첫번째 프로젝트에서는 @Json어노테이션 안에 있는 이름과 변수명이 같았고, 두번째 케이스는@Json어노테이션 안에 있는 이름명과 변수명이 다릅니다.

저는 당연히 이렇게 작성하더라도 스마트하게 다 알아서 해주겠지?라는 생각에 개발을 진행하던중 계속 Parent 클래스의 변수 값이 계속 null로 나오는걸 확인하였고 더 확인을 해보니 이미 Issue에도 등록이 되어있는 내용이었습니다.

내가 알지 못한 이유, 그리고 사람들의 의문

간단합니다 이유는.
일단 제가 위 예시처럼 작성한 이유는 알아서 Moshi가 해주겠지였습니다. 그리고 더더욱이 첫번째 케이스에서 알지 못했던 이유는 네트워크 요청으로 들어온 Json 필드 명이 변수명과 같아서 입니다.

그리고 Issue에 등록된 질문들도 이와 비슷합니다. 대체로 질문들은
앙? 이게 왜 안되? 버그아냐??? 뭐 잘못했어 내가? 아마 저와 같은 생각으로 접근한 개발자분들이 많은 것 같습니다.

문제점

부모 클래스에 정의한 @Json 어노테이션이 작동하지 않는다.

이유

너들이 자식클래스에 상속을 정의할때 기본으로 정의했자나, 그래서 그런거야, 문제점이 아니라 의도한대로 잘 작동한거야~

!?

아하, Child(...) : Parent()에 아무것도 넣지 않았기에 default로 설정한 null이 나왔다는 이야기 입니다.

Moshi의 관련 이슈들을 읽어보면 답변을 달아준 개발자들의 뉘앙스가 그걸 왜 그렇게 하는거야?라는 뉘앙스가 곧곧에서 보입니다.

해결방법?

Issue에 등록된 질문들에 대한 대답은 생각보다 간단합니다.

생성자에 필드들을 설정하지말고 클래스 내부에 설정해~ 이렇게는 가능해~입니다.

open class Parent() {
    @Json(name="fruit")
    var comp1: String? = null,
    
    @Json(name="name")
    var comp2: String? = null
}

핵심은 다음과 같습니다.
부모 생성자를 default로 해서 전달하면 Moshi는 모른다,

우리가 본능적으로 생각했던 방법으로 할려면 부모 클래서 안에서 정의해야한다

open class BaseCrawlingStock {
    @Json(name = Const.JSON_ISU_CD)
    var stockCode: String? = null

    @Json(name = Const.JSON_ISU_ABBRV)
    var name: String? = null

    @Json(name = Const.JSON_MKT_NM)
    var market: String? = null

    @Json(name = Const.JSON_CMPPREVDD_PRC)
    var diff: String? = null

    @Json(name = Const.JSON_FLUC_RT)
    var diffInRatio: String? = null
}

@JsonClass(generateAdapter = true)
data class StockByFluctuation(

    @Json(name = Const.JSON_BAS_PRC)
    var base: String,
    @Json(name = Const.JSON_CLSPRC)
    var closing: String,
    @Json(name = Const.JSON_ACC_TRDVOL)
    var volume: String,
    @Json(name = Const.JSON_ACC_TRDVAL)
    var amount: String,

) : BaseCrawlingStock()

BaseCrawlingStock 클래스를 보시면 생성자에 변수들을 두지않았습니다. 그리고 StockByFluctuation클래스는 기본생성자를 상속받도록 설정함과 동시에 @JsonClass(generateAdapter = true)로 설정했습니다.

위 어노테이션을 통해 생성된 어댑터 클래스를 살펴보면

public class StockByFluctuationJsonAdapter(
  moshi: Moshi
) : JsonAdapter<StockByFluctuation>() {
  private val options: JsonReader.Options = JsonReader.Options.of("BAS_PRC", "CLSPRC", "ACC_TRDVOL",
      "ACC_TRDVAL", "CMPPREVDD_PRC", "FLUC_RT", "MKT_NM", "ISU_ABBRV", "ISU_CD")

  private val stringAdapter: JsonAdapter<String> = moshi.adapter(String::class.java, emptySet(),
      "base")

위와 같고 만약에 superclass의 기본생성자에 변수들을 세팅할경우 어댑터는 다음과 같습니다.

public class StockByFluctuationJsonAdapter(
  moshi: Moshi
) : JsonAdapter<StockByFluctuation>() {
  private val options: JsonReader.Options = JsonReader.Options.of("BAS_PRC", "CLSPRC", "ACC_TRDVOL",
      "ACC_TRDVAL", "diff", "diffInRatio", "market", "name", "stockCode")

@Json어노테이션이 무시되고 해당 변수명 그대로 만들어지는것을 확인하실수있습니다.

관련이슈

이슈 766
이슈 700
이슈 495

profile
I will be the King of

0개의 댓글