Android

Gson으로 sealed class 로 맵핑하기

그란. 2022. 10. 4. 20:48

https://yuar.tistory.com/entry/API-Response-Map-%ED%98%95%ED%83%9C%EB%A1%9C-%EB%B0%9B%EA%B8%B0 

 

API Response Map 형태로 받기 ( Retrofit )

API로 받은 필드 값이 고정 타입이 아니라면 어떻게 받아야 할까? "type": "event", "info": { "eventId": 613, "eventUrl": "https://string.com", "eventName": "ttttt", "eventType": "offline" } "type": "pos..

yuar.tistory.com

 

Map형태로 받아서 맵핑 하는 방법도 있는데 

이번엔 바로 sealed class로의 맵핑을 해보려고 한다 

 

 

서버에서 받아온 Data 예시1)

{
	"context" : {
             "name" : "count",
              "param" : {
                "count": Int
            }
     }
}

서버에서 받아온 Data 예시2) 

{
	"context" : {
            "name" : "result",
            "param" : {
                "result": String
            }
    }
}

 

param은 두가지 종류로 올 수 있다.

 

data class Param(
	val count : Int?,
   	 val result :String?
)

이런 형태로 할수도 있겠지만 이러면 null 체크 계속 하게 되고 프로그래머가 계속 인지 하고 있어야 한다. ( 필드가 다수라면. )

 

하나의 형태로 묶어서 인스턴스 체크하는게 편리하기에. 아래와 같은 형태로 맵핑한다.

data class Context(
    val name: String,
    val param: Param
) {
    sealed class Param {
        data class A(
            val count: Int
        ) : Param()

        data class B(
            val result: String
        ) : Param()
    }
}

최종적으로는 이렇게 Context 클래스로 맵핑한다.

val response = gson.fromJson(model, Context::class.java)

 

하지만 이렇게 돌리면 에러가 발생한다 abstract class Param 은 파싱이 안된다고. 

( sealed class 가 내부적으론 abstract class 로 되어있나보다 ) 

 

직접 역직렬화 해줘야한다 

 

Gson을 생성한다. 

"Param 클래스를 찾으면 직접 정의한 ParamTypeAdapter으로 역직렬화한다."

GsonBuilder()
        .registerTypeAdapter(Param::class.java, ParamTypeAdapter)
        .create()

 

object ParamAdapter : JsonDeserializer<Context.Params?> {
    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): Context.Params? {
        if (json == null || context == null) return null
        val params = kotlin.runCatching {
            json.asJsonObject
        }.getOrNull() ?: return null
        return when {
            params.has("count") -> context.deserialize(
                params,
                Context.Params.A::class.java
            )
            params.has("result") -> context.deserialize(
                params,
                Context.Params.B::class.java
            )
            else -> null
        }
    }
}

 

일단 요기에서 json은 Param 객체를 지칭한다 ( 위에서 Param 객체를 만나면 이 클래스를 타라고 정의 했으니 ) 

json을 jsonObject ( gson 객체 ) 로 변환하고 

 

해당 객체가 count 를 가졌는지, result 를 가졌는지에 따라 따로 역직렬화 한다.

 


 

추가적으로 DI를 사용하고 Gson 의 용도가 다른 경우 ( A 서버에서 내려주는 데이터의 경우, B서버에서 내려주는 데이터의 경우 ) 

별도로 네이밍일 지정해줘야하는데 힐트,대거의 경우 annotation class 를 지정해준다

@Qualifier
@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.VALUE_PARAMETER,
    AnnotationTarget.FIELD
)
@Retention(AnnotationRetention.RUNTIME)
annotation class AServerQualifier

@Qualifier
@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.VALUE_PARAMETER,
    AnnotationTarget.FIELD
)
@Retention(AnnotationRetention.RUNTIME)
annotation class BServerQualifier

 

 

Gson 생성시 어노테이션 

@Singleton
@AServerQualifier
@Provides
fun provideGson(): Gson {
}

 

사용하는 곳(주입받는곳)에서 어노테이션

@AServerQualifier private val gson: Gson,