Android

API Response Map 형태로 받기 ( Retrofit )

그란. 2021. 10. 4. 23:53

API로 받은 필드 값이 고정 타입이 아니라면 어떻게 받아야 할까?

 

"type": "event",
"info": {
        "eventId": 613,
        "eventUrl": "https://string.com",
        "eventName": "ttttt",
        "eventType": "offline"
      }
"type": "post",
"info": {
        "postId": 12,
        "commentId": 1080,
        "postUserId": 2,
        "commentUserId": 781
      }

 

서버에서 "info" 필드는 타입에 따라 다른 필드들을 받는다. 

 

 

이런 경우 클라이언트에서는 info를 Map<String, Any> 형태로 받아서 처리하는 방법이다.

@SerializedName("type")
@Expose val type: String

@SerializedName("info")
@Expose val info: Map<String, Any>

 

클라이언트에서 사용할 모델 구조

data class Notification(
    val type: Type,
    val info: Info,
) 

sealed class Info {

         data class EventInfo(
            val eventId: Long,
            val eventUrl: String,
            val eventName: String,
            val eventType: String
        ) : Info()

        data class ProductInfo(
            val postId: Long,
            val commentId: Long,
            val postUserId: Long,
            val commentUserId: Long
        ) : Info()
}

 

Mapping ( GetNotification  -> Notification ) 

 NotificationData(
       type = type,
       info = mapToData(type, notification.info),
)

private fun mapToData(
        type: NotificationData.Type,
        content: Map<String, Any>
    ): NotificationData.Info {
        return when (type) {
           NotificationData.Type.EVENT -> {
                NotificationData.Info.EventInfo(
                    eventId = (content?.get("eventId") as? Double)?.toLong() ?: 0L,
                    eventUrl = (content?.get("eventUrl") as? String) ?: "",
                    eventName = (content?.get("eventName") as? String) ?: "",
                    eventType = (content?.get("eventType") as? String) ?: "",
                )
            }
      
            NotificationData.Type.POST -> {
                NotificationData.Info.PostInfo(
                    postId = (content?.get("postId") as? Double)?.toLong() ?: 0L,
                    commentId = (content?.get("commentId") as? Double)?.toLong() ?: 0L,
                    postUserId = (content?.get("postUserId") as? Double)?.toLong() ?: 0L,
                    commentUserId = (content?.get("commentUserId") as? Double)?.toLong() ?: 0L,
                )
            }
   }

각각의 타입에 맞게 직접 필드명으로 꺼내와서 타입캐스팅 하여 맵핑. 

(*주의할점은 서버에서 Int 값을 Retrofit 으로 가져올때 타입이 Double 타입이라는 것) 


추가로 응용버전으로 중첩 구조가 한단계 더 있는 경우에 대한 예제

 data class UsageQuestion(
            val answer: Answer
        ) : Question() {
            data class Answer(
                val correct: List<Correct>
            ) {
                data class Correct(
                    val text: String,
                    val wrong: String
                )
            }
        }

 

한뎁스에서 꺼내오고, 그 안의 뎁스에서 꺼내오기.

val answer = (question["answer"] as? Map<String, Any>)
val correct = (answer?.get("correct") as? List<Map<String, Any>>)

answer = EntryData.Question.UsageQuestion.Answer(
                        correct = correct?.map { correct ->
                            EntryData.Question.UsageQuestion.Answer.Correct(
                                text = (correct["text"] as? String) ?: "",
                                wrong = (correct["wrong"] as? String) ?: ""
                            )
                        } ?: emptyList()
                    )