서버 응답 원하는 값만 필터링하기

유재민·2022년 12월 5일
0
post-thumbnail
post-custom-banner

Retrofit으로 서버에 필요한 데이터를 요청한다. 데이터를 응답받을 때 응답 받은 데이터 중 필요한 데이터만 사용하고 싶은 경우가 있다. 예로 살펴보자.

{
    "coord": {
        "lon": 129.3343,
        "lat": 36.0982
    },
    "weather": [
        {
            "id": 800,
            "main": "Clear",
            "description": "clear sky",
            "icon": "01d"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 275.83,
        "feels_like": 271.91,
        "temp_min": 275.83,
        "temp_max": 275.83,
        "pressure": 1030,
        "humidity": 33,
        "sea_level": 1030,
        "grnd_level": 1026
    },
    "visibility": 10000,
    "wind": {
        "speed": 4.41,
        "deg": 297,
        "gust": 6.52
    },
    "clouds": {
        "all": 0
    },
    "dt": 1669948994,
    "sys": {
        "country": "KR",
        "sunrise": 1669932936,
        "sunset": 1669968513
    },
    "timezone": 32400,
    "id": 1832015,
    "name": "Heunghae",
    "cod": 200
}

위의 응답은 openweathermap api에서 응답받은 데이터이다. 여기서 우리가 사용해야할 데이터는 weather - id, main, iconmain - temp 이다. Application 단에서 이 부분만 받도록 가공해야한다.

Okhttp Interceptor

데이터를 가공하는 작업을 Interceptor를 통해서 할 수 있다. Application 단으로 가기 전 데이터를 가로채서 가공한 후 필요한 데이터만 제공해주는 작업을 진행할 것이다.

  1. 서버의 응답을 가로채어 수신 값을 jsonString 형태로 받아온다.
val request = chain.request()
val response = chain.proceed(request)
val jsonString = response.body?.string() ?: ""
  1. string을 객체로 decode 해준다.
val json =Json{ignoreUnknownKeys = true}
val result = json.decodeFromString<WeatherContainerResponse>(jsonString)

ignoreUnknownKeys 를 true로 설정하여 사용하지 않는 속성은 정의할 필요가 없도록 설정해준다.

kotlinx serialization은 decodeFromString 메서드를 활용하여 객체로 변환할 수 있지만 gson이라면 TypeToken을 통해 객체화 작업을 진행해야한다.

  1. decode된 객체를 원하는 데이터로 가공하여 body에 넣어준다.
response.newBuilder()
    .message(response.message)
    .body(Json.encodeToString(
        WeatherResponse(
            weather.id,
            weather.type,
            weather.icon,
            main.temperature
        )
    ).toResponseBody())
    .build()

weather의 속성과 main의 속성을 결합해야하는 구조라서 WeatherResponse 라는 데이터 클래스로 만들어 body에 넣어주었다.

전체 코드

@Provides
@Singleton
@Named("Filtering")
fun provideFilteringInterceptor(): Interceptor {
    return Interceptor { chain->
				val request = chain.request()
        val response = chain.proceed(request)
        val jsonString = response.body?.string() ?: ""
        val json =Json{ignoreUnknownKeys = true}
				val result = json.decodeFromString<WeatherContainerResponse>(jsonString)
        val weather = result.weathers.first()
        val main = result.main
        response.newBuilder()
            .message(response.message)
            .body(Json.encodeToString(
                WeatherResponse(
                    weather.id,
                    weather.type,
                    weather.icon,
                    main.temperature
                )
            ).toResponseBody())
            .build()
		}
}
@Provides
@Singleton
@Named("Weather")
fun provideWeatherOkHttpClient(
    loggingInterceptor: HttpLoggingInterceptor,
    @Named("Weather") weatherHeaderInterceptor: Interceptor,
    @Named("Filtering") filteringInterceptor: Interceptor
): OkHttpClient {
    return OkHttpClient().newBuilder()
        .addInterceptor(loggingInterceptor)
        .addInterceptor(weatherHeaderInterceptor)
        .addInterceptor(filteringInterceptor)
        .addNetworkInterceptor(StethoInterceptor())
        .build()
}

addInterceptor 로 filteringInterceptor를 추가해준다. Hilt를 사용했고 여러 Interceptor가 있기 때문에 @Named 애노테이션으로 이름을 지정해주었다.

@Serializable
data class WeatherResponse(
    val id: Long,
    val type: String,
    val icon: String,
    val temperature: Double,
)

@Serializable
data class WeatherTypeResponse(
    @SerialName("id") val id: Long,
    @SerialName("main") val type: String,
    @SerialName("icon") val icon: String
)

@Serializable
data class TemperatureResponse(
    @SerialName("temp") val temperature: Double
)

@Serializable
data class WeatherContainerResponse(
    @SerialName("weather") val weathers: List<WeatherTypeResponse>,
    @SerialName("main") val main: TemperatureResponse
)
@GET("/data/2.5/weather")
suspend fun getWeatherData(
    @Query("lat") latitude: Double,
    @Query("lon") longitude: Double,
): WeatherResponse

Application 단에서 반환되는 값은 필요한 데이터만 가공된 WeatherResponse 이다. DataSource나 Repository에서 데이터 가공을 할 필요 없어진 것을 확인할 수 있다.


참고자료

🍒서버 응답 Cherry Pick!🍒 (OkHttp Interceptor)

profile
유잼코딩
post-custom-banner

0개의 댓글