티스토리 뷰

반응형
 

최근에는 대부분 API 응답이 JSON 형식으로 다양한 데이터를 전달합니다.
Rest API 통신 Data로 다양한 형태를 받을 수 있습니다.


하나의 예시를 들어보겠습니다. 만약, API 응답이 다음과 같은 형태로 주어진다면 이를 어떻게 처리할 수 있을까요

[
    {
       "type": "TRUCK",
       "waterCannon": true
    },
    {
       "type": "PLANE",
       "wingsSpanInMeters": 20
    }
 ]

 

Response Dto 를 다음과 같이 구성해서 받을 준비는 하고 있지 않은지 묻고 싶습니다.

class Veichle {
	type : String
	waterCannon : Boolean
	wingsSpanInMeters : Int
}

 

예시로 TRUCK 과 PLANE 을 두었는데요.
만약 BIKE, TAXI 와 같이 계속 증가한다면 Veichle class의 값들은 많아지고 Veichle의 역할은 모호해지게 됩니다.
어떻게 하면 다형성을 유지하면서 Single Json Response 을 받을 수 있을까요?

 

이 고민을 풀어줄 3th part libray 인 Moshi 를 제공하고 있습니다. 이번 시간은 Moshi 라이브러리에 대해서 간단히 알아보겠습니다.

 


 

Moshi Library 맛보기

Moshi 는 Signle Json Response 에서 다형성을 유지하도록 도와주는 3th part Library 입니다.

Moshi를 사용하기 위해 app.bundle 에 dependencies Moshi 를 등록해보겠습니다.

dependencies{
    // Moshi
    implementation 'com.squareup.moshi:moshi:1.14.0'
    implementation 'com.squareup.moshi:moshi-adapters:1.14.0'
    implementation 'com.squareup.moshi:moshi-kotlin:1.12.0'
	implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
}

 

 

Dependencies 구성이 완료 되었으면, Vehicle의 JSON 데이터를 Moshi를 사용하여 가져오기 위한 DataContainer 설정 방법을 설명드리겠습니다.

 

먼저, VehicleType enum class 를 선언하여 TRUCK 과 PLANE 을 설정합니다. 이렇게 하면 Mosh에서 VehicleType 을 활용해서 타입을 구분 할 수 있습니다.

 

그 다음 JSON 파싱을 위한 Data class 을 생성합니다. 여기서 주의할 점은 @JsonClass(generateAdpater) 인 Annotation을 선언하는 것입니다. 또한, 각 JSON 필드는 @JSON Annotation 을 선언하여 파싱할 수 있도록 합니다.

지금까지 설명한 DataContainer 는 다음과 같습니다.

/***
 *  Vehicle
 */
enum class VehicleType {
  TRUCK,
  PLANE
}

interface Vehicle {
    val type: VehicleType
}

@JsonClass(generateAdapter = true)
data class Truck(
    @Json(name = "waterCannon") val waterCannon: Boolean
) : Vehicle {
    override val type = VehicleType.TRUCK
}

@JsonClass(generateAdapter = true)
data class Plane(
    @Json(name = "wingsSpanInMeters") val wingsSpanInMeters: Int
) : Vehicle {
    override val type = VehicleType.PLANE
}

inline fun <reified T> Moshi.parseList(jsonString: String): List<T>? {
    return adapter<List<T>>(Types.newParameterizedType(List::class.java, T::class.java)).fromJson(jsonString)
}

 

 

이제 데이터 파싱을 위한 Moshi 설정 방법을 설명하겠습니다.


Moshi 는 다형성을 지원하기 위해 PolymorphicJsonAdapterFacotry 를 사용할 수 있습니다.
이를 통해 JSON 데이터의 특정 필드 값을 기준으로 여러 타입의 객체를 구분할 수 있습니다.

 

Vehicle 클래스를 설정하고, type 필드를 구분자로 사용하여 waterCannonwingsSpanInMeters 설정할 수 있습니다.

val vehicleFactory = PolymorphicJsonAdapterFactory.of(Vehicle::class.java, "type")
            .withSubtype(Truck::class.java, VehicleType.TRUCK.name)
            .withSubtype(Plane::class.java, VehicleType.PLANE.name)
val moshi = Moshi.Builder()
    .add(vehicleFactory)
    .add(KotlinJsonAdapterFactory())
    .build()

val response = "[\n" +
        "    {\n" +
        "       \"type\": \"TRUCK\",\n" +
        "       \"waterCannon\": true\n" +
        "    },\n" +
        "    {\n" +
        "       \"type\": \"PLANE\",\n" +
        "       \"wingsSpanInMeters\": 20\n" +
        "    }\n" +
        " ]"

val vehicleList = moshi.parseList(response) ?: kotlin.run {
    print("Error Null")
    return
}

for ((index, vehicle) in vehicleList.withIndex()) {
    when (vehicle.type) {
        VehicleType.PLANE -> {
            val wingsSpanInMeters = (vehicle as Plane).wingsSpanInMeters
            Log.d("@@@ VehicleTest @@@", "inedx = $index, wingsSpanInMeters = $wingsSpanInMeters")
        }
        VehicleType.TRUCK -> {
            val waterCannon = (vehicle as Truck).waterCannon
            Log.d("@@@ VehicleTest @@@", "inedx = $index, waterCannon = $waterCannon")
        }
    }
}

 

 

Q. key 로 선언 한 값이 없는 경우 어떻게 동작하나요?
A. Moshi 를 사용 했을 때 API Response 에서 key 가 존재하지 않으면 Exception 발생하게됩니다.
만약 Retrofit 을 활용하여 Moshi 를 구현 하였을 경우는 API Fail 처리 됩니다.

 


 

Retrofit 을 활용한 Moshi 구성 :Step-by-Step

Retrofit 에서 Moshi 을 활용해서 Data 의 다형성을 지원하는 환경 구성할 수 있습니다.

 

1. 의존성 추가

먼저, Moshi 라이브러리와 함께 Retrofit를 프로젝트에 추가해야 합니다.
build.gradle 파일에 다음과 같이 추가합니다.

dependencies{
    implementation "com.squareup.moshi:moshi:1.14.0"
    implementation 'com.squareup.moshi:moshi-adapters:1.14.0'
    implementation("com.squareup.moshi:moshi-kotlin:1.12.0")

    // Retrofit2, Gson converter, Gson
    implementation 'com.squareup.retrofit2:retrofit:2.9.
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'

}

 

2. Moshi 설정

Moshi를 설정합니다. 다형성을 지원하기 위해 PolymorphicJsonAdapterFactory를 사용할 수 있습니다.

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory

val moshi = Moshi.Builder()
	 .add(
	     PolymorphicJsonAdapterFactory.of(Vehicle::class.java, "type")
	        .withSubtype(Truck::class.java, VehicleType.TRUCK.name)
	        .withSubtype(Plane::class.java, VehicleType.PLANE.name)
	 )
	 .add(KotlinJsonAdapterFactory())
	 .build()

 

3. Retrofit 설정

Retrofit을 설정하여 Moshi를 JSON 변환기로 사용합니다.

import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory

val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com/") // API의 기본 URL
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .build()

 

4. API 인터페이스 정의

API 엔드포인트를 정의하는 인터페이스를 작성합니다.
Retrofit의 애노테이션을 사용하여 HTTP 메서드와 엔드포인트를 지정합니다.

interface ApiService {
    @GET("REST API PATH")
    suspend fun getVehicles(): Response<List<Vehicle>>?
}

 

5. API 호출

Retrofit 인스턴스에서 API 인터페이스의 구현을 생성하고 API를 호출합니다.

class MainViewModel : ViewModel(){
    fun startRestAPI(){
			viewModelScope.launch {
				val apiService: ApiService = retrofit.create(ApiService::class.java)
			  val model = apiService.getVehicles()
	    }
	  }
}

 

이렇게 구성하면 Moshi와 Retrofit을 사용하여 API 클라이언트를 구성하고 API를 호출할 수 있게 됩니다.
이로써Moshi의 다형성 지원 기능을 활용하여 다양한 타입의 JSON 데이터를 쉽게 파싱할 수 있습니다.

 



마무리

Moshi 를 활용하면 다양한 타입의 JSON 을 파싱 할 수 있습니다.
특히, 다형성 JSON 데이터를 다룰 때 PolymorphicJsonAdapterFactory를 사용하여 쉽게 처리할 수 있습니다.

API 구성 시 type을 통한 데이터 구성이 많을 경우 Moshi의 도입은 생각해 볼 필요가 있습니다.
Moshi는 직관적인 설정과 강력한 기능 제공으로 복잡한 JSON 구조도 간단하게 파싱할 수 있도록 도와줍니다.

 

Retrofit 에서도 Moshi의 활용은 고려해볼 필요가 있습니다. Moshi의 어댑터를 통해 다양한 JSON 형식을 유연하게 처리할 수 있으며, Moshi는 Kotlin 지원도 뛰어나므로 Kotlin 프로젝트에서 자연스럽게 통합하여 사용 할 수 있습니다.

 

API 설계나 유지보수 시 다양한 타입의 JSON을 다뤄야하다면, Moshi 도입은 고려하는 것을 추천드립니다. 그로 인해 코드의 가독성 및 유지보수성이 높이고, 더 나은 데이터 파싱 경험을 할 수 있을것으로 생각됩니다.

 



참고

moshi GitHub

dynamic-json-deserialization-moshi-retrofit Blog Post

반응형
댓글