객체 자체를 직렬화, 저장하고 다시 불러오기

Gson 라이브러리 추가


Gson은 자바 객체를 Json 형태로 변환해주는 라이브러리이다.

Gson is a Java library that can be used to convert Java Objects into their JSON representation


아래 의존성을 추가해주자.

dependencies {
  implementation 'com.google.code.gson:gson:2.9.1'
}


Object -> Json 변환

아래 코드는 간단하게 java object 를 json으로 바꿔준다.

val gson = Gson()
val json = gson.toJson(Plane.getInstance())


Json -> Object 변환

위에서 만들었던 json 변수를 사용해 다시 객체를 복원할 수 있다.

val plane = gsonForPlane.fromJson(json, Plane::class.java)


Issue

Interfaces can’t be instantiated!


위 방법은 간단하게 객체를 json으로 변환하지만 인터페이스 를 포함하는 경우 위와 같은 오류가 발생한다.

Plane 클래스는 ShapeModel 인터페이스를 상속한 여러 타입을 관리하기 위해 아래 맴버를 갖고있으며 interfaces can’t be instantiated! 에러가 발생한다.


// Plane.kt
class Plane {
    private val rectModelList = mutableListOf<ShapeModel>()
    ...
}

// ShapeModel.kt
interface ShapeModel {
    ...
}


해결 방법

TypeAdatper: JsonSerializer<T>, JsonDeserializer<T>


인터페이스를 직렬화 하기 위해서는 JsonSerializer<T>, JsonDeserializer<T> 두 인터페이스를 구현하는 TypeAdatper 클래스를 만들어야한다. (T 지네릭 타입 = 인터페이스 타입)


타입 어뎁터 클래스까지 만들었다면 위에서 사용하던 Gson() 생성자 대신 GsonBuilder() 빌더 패턴을 활용해 타입 어뎁터 클래스를 등록하면 된다.


class ShapeTypeAdapter: JsonSerializer<ShapeModel>, JsonDeserializer<ShapeModel> {
    override fun serialize(
        src: ShapeModel?,
        typeOfSrc: Type?,
        context: JsonSerializationContext?
    ): JsonElement {
        return JsonObject().apply {
            addProperty(CLASSNAME, src?.javaClass?.name)
            add(DATA, context?.serialize(src))
        }
    }

    override fun deserialize(
        json: JsonElement?,
        typeOfT: Type?,
        context: JsonDeserializationContext?
    ): ShapeModel {
        val jsonObject = json?.asJsonObject
        val className = jsonObject?.get(CLASSNAME)?.asString
        val objectClass = getObjectClass(className)
        return context!!.deserialize(jsonObject?.get(DATA), objectClass)
    }

    private fun getObjectClass(className: String?): Class<*> {
        try {
            return Class.forName(className)
        } catch (e: ClassNotFoundException) {
            throw JsonParseException(e)
        }
    }

    companion object {
        private const val CLASSNAME = "CLASSNAME"
        private const val DATA = "DATA"
    }
}


// 어뎁터 등록 gson 객체
val gson =  GsonBuilder()
            .registerTypeAdapter(ShapeModel::class.java, ShapeTypeAdapter())
            .create()