업비트 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에 대해 짧게만 정리하고 가겠습니다.
하나의 디자인 패턴입니다.
쉽게 예를 들 수있는게 아마도 여행을 갈때 노트북 충전기를 떠올시면 될 거 같습니다.
노트북 충전기는 아래와 같이 작동하는데
이때 네모난 플라스틱 케이스를 어댑터라고 부릅니다.
프로그래밍에서도 이 어탭터는 자주 쓰입니다.
안드로이드를 예를 들자면 가장 쉽게 볼 수 있는게 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로 해당 변수만 교체하기로 내용 업로드 하였습니다.