개발/코틀린

Kotlin object와 Gson의 오해

DinoDev 2023. 8. 3. 14:03
728x90
반응형

안녕하세요. 안드로이드 개발자 정석준입니다.

 

오늘 소개할 내용은 Kotlin에서 Gson을 사용할 때 object로 json을 파싱하는 경우 발생하는 버그에 대해 이야기해보려고 합니다.

 

예제코드

import com.google.gson.Gson

fun main() {
    val gson = Gson()
    val json = "{}"
    val house: House = gson.fromJson(json, House.Room::class.java)
    val name = when (house) {
        House.Living -> "Living"
        House.Room -> "Room"
    }
    println(name)
}

sealed class House {
    object Room : House()
    object Living: House()
}

 

이 예제코드에서 어떤 문자열이 출력 될 것으로 예상되시나요?
저는 처음에 당연히 Room이 출력될 것으로 생각하고 코드를 작성했습니다.

 

 

하지만 코틀린에서는 NoWhenBranchMatchedExceeption이 발생했습니다.

도대체 이게 무슨 Exception이지? 라는 생각이 들었습니다.

문제가 뭘까?

Gson을 사용해서 생성한 House.Room이 object House.Room과 다른 인스턴스를 가지는 것이 문제였습니다.

 

import com.google.gson.Gson

fun main() {
    val gson = Gson()
    val json = "{}"
    val house: House = gson.fromJson(json, House.Room::class.java)
    println(house) // House$Room@8e63b0f5
    println(House.Room) // House$Room@61c63069
}

sealed class House {
    object Room : House()
    object Living: House()
}

 

Kotlin에서 object는 단 하나의 인스턴스만 가지는데 왜 House.Room은 두 개가 됐을까요?

그 이유는 Gson에서 House.Room 인스턴스를 새로 만들어서 전달해 주기 때문입니다.

Gson의 내부 코드를 쭉 타고 들어가면 결국 Java의 reflection을 이용해 newInstance를 호출해주고 있습니다.

해결 방법

1. is를 사용하자

is를 사용하면 간단하게 해결이 됩니다. 그럼 왜 is를 사용하면 해결이 될까요?

위에서 본 것처럼 파싱된 House.Room과 object로 선언된 House.Room의 인스턴스가 다르기 때문에 type check로 검사하면 됩니다.

 

 

하지만 우리는 when에 sealed class를 사용할 때 Add remaining branches 기능으로 자동으로 코드를 추가하게 됩니다.

이런 경우에는 is가 자동으로 추가되지 않기 때문에 문제를 컴파일 시점에 발견하기 어렵습니다.

 

 

그렇다면 이 문제를 어떻게 해결하면 좋을 까요?

2. object 대신 class를 사용하자

class 내부에 필드가 없는 경우 object로 선언하는 경우가 많은데 이런 경우를 피하기 위해서 class로 작성하는 게 도움이 될 수 있습니다.

sealed class House {
    class Room : House()
    class Living: House()
}

 

3. data object를 사용하자

kotlin 1.9.0에서 stable하게 추가 된 data object를 사용해서 해결 할 수 있습니다.

https://kotlinlang.org/docs/whatsnew19.html#stable-data-objects-for-symmetry-with-data-classes

 

What's new in Kotlin 1.9.0 | Kotlin

 

kotlinlang.org

sealed class House {
    data object Room : House()
    data object Living: House()
}

 

 


블로그 글에 대해 궁금한 점이 있다면 아래 카카오톡 오픈채팅에 들어와서 질문해주세요

 

Android Kotlin Compose QnA

 

open.kakao.com

 

728x90
반응형