Gson, 제네릭 타입 역직렬화 이슈
Search

Gson, 제네릭 타입 역직렬화 이슈

생성일
2023/02/24 07:31
태그

이슈

DataStore를 이용해 ArrayList<Int> 값을 보관하려 하는데 문제가 발생했다. 바로 Int 값이 Double로 변경되는 현상이 있다. 분명 56이라는 숫자가 56.0의 Double형으로 변경되어 있는 것이다.
gSon.fromJson(json, ArrayList<Int>()::class.java) gSon.fromJson(json, ArrayList<Int>().javaClass)
Kotlin
복사
위와 같이 작성한다면 반환 받은 ArrayList의 숫자들은 소수점이 생기며 Integer형이 Double형으로 바뀌어 있을 것이다.

해결 방법

해결 방법은 아주 간단하다.

방법1. TypeToken

gSon.fromJson<ArrayList<Int>>(json, object : TypeToken<ArrayList<Int>>() {}.type)
Kotlin
복사
TypeToken으로 ArrayList<Int>를 명시해 주면 된다.

방법2. toInt()

getData().map { it.toInt() } // or getData().map {it}
Kotlin
복사
map { it.toInt() } 처리 해줘도 되지만 IDE에서는 이미 Int형이라 굳이 해줄 필요 없다며 회색으로 표시해준다. 불편하다면 map { it } 까지만 해줘도 된다.

원인

왜 이런 문제가 발생 했는지 원인을 알아야 하는데, Java의 Type Erasure(타입 제거)로 인해 발생한다.
제네릭은 컴파일 타임에 Type을 체크하기 때문에 런타임에는 체크하지 않는다. 런타임에도 제네릭 타입 정보를 유지하려면 별도로 명시해줘야 한다. (Gson: Type Token)

Oracle 문서

Java에서 타입 제거를 하는 이유는 런타임에서 타입 체크를 하지 않고 성능상 이점을 가져가려는 이유다.
오라클의 자바 문서를 보면 관련된 내용이 설명되어 있다.

Gson 내부 처리 로직

내부 로직을 보면 NUMBER 타입의 경우는 toNumberStrategy.readNumber(in)을 반환한다.
readNumber()의 내부를 보면 Long으로 파싱이 가능하지 않으면, Double로 반환해주는 것을 볼 수 있다.

Gson 문서

Gson 번역 문서를 보면, Type Erasure(타입 제거)에 대한 내용이 있다.

Gson 주석 일부

요약하자면 이렇다 ”일반적인 경우에는 잘 작동하지만 제네릭 타입의 경우에는 {@link #fromJson(JsonElement, TypeToken)} 를 사용해야 한다.”

Gson User Guide

문서를 요약하자면 아래와 같다.
“일반적으로 json, MyClass.class를 인수로 전달할 수 있다. 이것은 비제네릭 유형 객체인 경우에는 잘 작동된다. 그러나 객체가 제네릭 유형인 경우 Java Type Erasure로 인해 일반 유형 정보가 손실된다. 위의 코드는 Gson이 list.getClass()를 호출하여 클래스 정보를 가져오지만, 이 방법은 Foo.class와 같은 원시 클래스를 반환한다. 이는 Gson이 이것이 Foo<Bar> 유형의 객체이며 단순히 Foo가 아니라는 사실을 알 수 없다는 것을 의미한다. 이 문제를 제네릭 유형에 대한 올바른 매개변수화된 유형을 지정하여 해결할 수 있다. 이를 위해 TypeToken 클래스를 사용할 수 있다.”

참고