Moshi, Adapter를 활용하여 원하는 객체 만들기

Jinho Shin·2021년 9월 15일
1

안드로이드

목록 보기
2/4

업비트 API를 활용하여 간단하게 개인 프로젝트를 진행하던 와중에 생긴 일에 대한 해결법을 공유하려고 합니다.

업비트 API에는 상장된 모든 가상화폐에 대한 간략한 정보를 가져오는 API가 있습니다.

[
    {
        "market_warning": "NONE",
        "market": "KRW-BTC",
        "korean_name": "비트코인",
        "english_name": "Bitcoin"
    },
    {
        "market_warning": "NONE",
        "market": "KRW-ETH",
        "korean_name": "이더리움",
        "english_name": "Ethereum"
    },
    
    ...
]

요청 결과값을 보고 모두다 문자열이기에 해당 JSON을 변환할때 요청 결과 값 자료형을 그대로 반영하여 data class를 만들었습니다.

/**
 * 아래는 Room library를 사용하였습니다.
 */
@Entity(tableName = Const.TABLE_CRYPTO_CURRENCY)
data class CryptoCurrency(

    @Json(name = Const.JSON_UPBIT_TICKER)
    val ticker: String,
    @Json(name = Const.JSON_UPBIT_TICKER_NAME)
    val name: String,
    @Json(name = Const.JSON_UPBIT_TICKER_NAME_ENG)
    val nameEng: String,
    @Json(name = Const.JSON_UPBIT_MARKET_WARNING)
    val hasWarning: String
)

아직은 개인프로젝트 단계가 초기라서 이렇게 놔둬도 상관습니다. 하지만 API요청 결과값 예시에 market_warning을 좀 더 자세히 보니, 결국 해당 가상화폐가 문제가 있다, 없다로 나누어 지는걸 확인하였고, Boolean값으로 표현하는게 더 맞다는 판단을 하게 되었습니다. 더불어 나중에 저 변수로 if else를 처리할 경우가 생긴다면 if(currency.hasWarning == "warning") 방법보다는 해당 변수 자료형만 Boolean으로 만들어서if(currency.hasWarning)로 하는게 덜 번거롭습니다.

어떻게 할 수 있을까요?

그전에 우리가 프로그래밍을 하면서 자주 접하게 되는 Adapter에 대해 짧게만 정리하고 가겠습니다.

Adapter

하나의 디자인 패턴입니다.
쉽게 예를 들 수있는게 아마도 여행을 갈때 노트북 충전기를 떠올시면 될 거 같습니다.
노트북 충전기는 아래와 같이 작동하는데

  1. 전기를 받아온다
  2. 네모난 플라스틱 케이스를 거쳐 노트북이 받아들 일 수 있는 전력으로 만들어진다.
  3. 노트북은 그 전력을 받아 자기 자신을 충전시키다.

이때 네모난 플라스틱 케이스를 어댑터라고 부릅니다.

프로그래밍에서도 이 어탭터는 자주 쓰입니다.

안드로이드를 예를 들자면 가장 쉽게 볼 수 있는게 RecyclerViewAdapter입니다.
RecyclerViewAdapter도 결국 item를 받아와서item을 반영한 View를 만들어 주는 역할 입니다.

다시 돌아와서

저의 개인 프로젝트에서는 Moshi라는 JSON라이브러리를 사용합니다. 여타 JSON라이브러리(GSON)처럼 Adapter를 지원하는데 결국 이 Adapter의 기능도 앞에서 설명 드린바와 같이 동일합니다.

네워크 요청으로 가져온 객체 A -> Moshi에 설정한 어댑터 -> 내가 원하는 객체B

여기서 객체 A는 업비트 API를 통해 받아온 초기 모델이 될 것이고, 객체 B는 어댑터를 통해 나온 제가 원하는 객체 입니다.

그럼 객체 A, B를 다시 써보자면

객체A
data class CryptoCurrencyJson(

    @Json(name = Const.JSON_UPBIT_TICKER)
    val ticker: String,
    @Json(name = Const.JSON_UPBIT_TICKER_NAME)
    val name: String,
    @Json(name = Const.JSON_UPBIT_TICKER_NAME_ENG)
    val nameEng: String,
    @Json(name = Const.JSON_UPBIT_MARKET_WARNING)
    val hasWarning: String
)

객체B
@Entity(tableName = Const.TABLE_CRYPTO_CURRENCY)
data class CryptoCurrency(

    @Json(name = Const.JSON_UPBIT_TICKER)
    val ticker: String,
    @Json(name = Const.JSON_UPBIT_TICKER_NAME)
    val name: String,
    @Json(name = Const.JSON_UPBIT_TICKER_NAME_ENG)
    val nameEng: String,
    @Json(name = Const.JSON_UPBIT_MARKET_WARNING)
    val hasWarning: Boolean
)

이제 마지막 어댑터만 정의하고 Moshi를 사용할때 추가해주기만 하면 됩니다.

class CryptoCurrencyAdapter {

    @FromJson
    fun fromJson(currency: CryptoCurrencyJson): CryptoCurrency {
        return CryptoCurrency(currency.ticker, currency.name, currency.nameEng,
        currency.hasWarning == Const.UPBIT_WARNING)
    }

    @ToJson
    fun toJson(currency: CryptoCurrency): CryptoCurrencyJson {
        return CryptoCurrencyJson(currency.ticker, currency.name, currency.nameEng,
            if(currency.hasWarning) Const.UPBIT_WARNING else Const.UPBIT_NONE)
    }
}

작성하실 때 @FromJson, @ToJson 어노테이션을 달아서 어떤걸로 변환을 시켜줄지 명시하기만 하면 됩니다. 만약 둘중에 하나만 필요하시면 하나만 쓰셔도 무관합니다.

마지막으로 Moshi에 이 어댑터를 추가만 하면 끝입니다.

Moshi 예시
private val moshi = Moshi.Builder()
    .addLast(KotlinJsonAdapterFactory())
    .add(DateAdapter())
    .add(CryptoCurrencyAdapter())
    .build()

Retrofit 인터페이스 예시
@GET(Const.PATH_UPBIT_GET_ALL_TICKERS)
    suspend fun getAllTickers(@Query(Const.UPBIT_GET_ALL_TICKERS) isDetailed: Boolean): Response<List<CryptoCurrency>>    

이러한 방법으로 굳이 문자열을 if(멤버변수 값 == "문자열")을 하는 번거로움을 피하게 되었습니다.

초기에는 해당 변수만 바꿀 수 있는 방법을 찾아보았는데 찾지는 못했습니다.

혹시라도 방법을 알고계시다면 공유해 주시면 감사하겠습니다.


2021.09.17 추가
@JsonQualifier로 해당 변수만 교체하기로 내용 업로드 하였습니다.

profile
I will be the King of

0개의 댓글