Android & Kotlin

Serialization 라이브러리 마이그레이션 과정(Gson -> Kotlinx-Serialization)

쉽코기 2023. 12. 23. 11:42

개요

  • 위 글에서는 Gson 에서 Kotlinx-Serialization 으로 의 마이그에션 도중 겪은 문제 및 그 과정에서 얻게된 정보들을 정리합니다.
  • 마이그레이션 배경에 대해서는 이전 글을 참고해주세요

 

📌 마이그레이션 순서

  1. 라이브러리 선언 (각 모듈 별로 선언해야함)
  2. 모델 변경 (어노테이션 수정), Adapter (Serializer) 정의
  3. retrofit converter 정의 및 추가
  4. 통신 이외 부분에서도 Gson 걷어내기 (optional)

(크게는 위 순서대로 진행했지만 마이그레이션을 실제로 진행하다보면 부분부분이 물려있어 왔다갔다하며 수정이 필요하다.)

 


 

1.  라이브러리 선언

- serialization 라이브러리 선언

- retrofit converter 추가 (왓슨형 감사합니다 ㅜㅜ)

- 모듈화가 되어 있다면 모듈 별로 라이브러리 추가가 필요하다.

📍 의문 : 왜 하위 모듈에서 추가한 라이브러리를 상위 모듈에서 또 추가해야하는 거지??

gradle 에서 라이브러리를 추가하는 모듈 키워드인 implementation 은 해당 모듈에서만 참조 라이브러리의 접근을 가능케한다. 이는 모듈화의 핵심인 모듈 인터페이스만이 외부로 노출되어야하는데 라이브러리의 인터페이스까지 노출되어서는 안된다는 개념을 구현한 것이라고 생각할 수 있다. 추가로 api(과거 엔 compile) 키워드를 통해 하위 모듈의 라이브러리를 참조할 수 있긴하지만 금기시되는 방식이라고한다. 아래 링크에 gradle 문서에 대한 해석과 각각의 키워드 설명이 있다. 참고하자

https://jongmin92.github.io/2019/05/09/Gradle/gradle-api-vs-implementation/

 

(Gradle dependency) api와 implementation 차이

build script의 dependencies 블록에 여러 가지 다양한 종속성 구성(api, implementation, compileOnly, runtimeOnly, annotationProcessor)을 사용하여 라이브러리 종속성을 선언할 수 있습니다. 다양한 종속성 구성 중 api

jongmin92.github.io

 

2. 모델 변화 / Adapter 정의

  • 모델 변경시 어노테이션 수정이 필요하다 -> 노가다성이니 최대한 스튜디오를 잘 활용해서 한번에 바꾸고 추가하는 것이 좋다

 

Aadapter (Serializer 정의)

 

 Gson 에서는 라이브러리 내부에서 지원하는 객체 형식을 kotlinx-serialization 에서는 지원하지 않는 경우가 존재한다. 나의 경우에는 BigDecimal, Numbe, Any 등등 이었다. 이는 KSerializer 를 구현하여 커버할 수 있다. 추가로 data Layer 에서 domain 으로 값을 변경해주는 mapper 에 중복 로직을 Serializer 를 정의함으로써 많이 줄일 수 있었다. 예를 들어 Group 이라는 객체가 여러 API 에서 쓰이고 이때 member_amount 가 hex 값으로 내려온다면 내부에서 사용을 위해서 같은 컨버팅 로직이 들어가고 유지보수에도 좋지않다. 이를 Serializer 로 정의 해놓고 이안에서 mapper 로직을 넣어 놓는 방법도 생각해볼 수 있게다. 특히 날짜 관련해서도 사용성이 좋을듯하다.

 

class DynamicLookupSerializer : KSerializer<Any> {
    override val descriptor: SerialDescriptor =
        ContextualSerializer(Any::class, null, emptyArray()).descriptor

    @OptIn(InternalSerializationApi::class)
    override fun serialize(encoder: Encoder, value: Any) {
        val actualSerializer =
            encoder.serializersModule.getContextual(value::class) ?: value::class.serializer()
        encoder.encodeSerializableValue(actualSerializer as KSerializer<Any>, value)
    }

    override fun deserialize(decoder: Decoder): Any {
        error("Unsupported")
    }
}
class BigIntegerSerializer : KSerializer<BigInteger> {
    override val descriptor: SerialDescriptor =
        PrimitiveSerialDescriptor("BigInteger", PrimitiveKind.INT)

    override fun serialize(encoder: Encoder, value: BigInteger) =
        encoder.encodeString(value.toString())

    override fun deserialize(decoder: Decoder): BigInteger = BigInteger(decoder.decodeString())
}

 

3.  retrofit converter 정의 및 추가

  • convter 는 요 라이브러리는 추가하여 사용하면된다.

 

Convter 옵션

  • 기존 사용성 유지 및 SideEffect 를 최소화하기 위한 옵션 설정이 필요하다. 주요 옵션을 정리해보면 아래와 같고 그 외 옵션 또한 한번씩은 검토해보자

ignoreUnknownKeys = ture

 - json string 에는 정의되어 있으나 model 에 없는 케이스 에러 무시

 

isLenient = true

 - JSON 규격 제한(RFC-4627)이 제거

 - 기본적으로 JSON에서 사용되는 key 값은 따옴표(””)로 감싸져야 하고 value 또한 enum, string 타입일 경우 따옴표로 감싸져야 함

하지만 isLenient를 true를 통해 느슨한 규칙 적용

 

explicitNulls = false

- Json 문자열에 대해서 property 값의 null 인 경우 필드 값을 포함하지 않음

- 미설정시 400 오류를 뱉는 케이스가 존재함

 

📍Gson Converter 와 Kotlin-Serialization converter 혼용이 가능할까?

 

마이그레이션 진행중에 이런생각이들었다. 몇몇 Gson 사용 영역은 그대로 남겨두고 converter 도 남겨두되 Kotlin-Serialization converter 에서 처리되지 않는 부분은 Gson Converter 에서 처리하게끔 하면 안될까? 결론은  두 conveter 의 양립은 존재할 수 없다.

  • Retrofit 은 순차적으로 converting 가능한 converter를 찾음 (가능 여부 null 반환으로 판단)
  • Gson 과 Kotlin-serialization converter 모두 null 을 반환하지 않고 불가할 경우 예외를 던짐
  • Gson 과 Kotlin-serialization converter 모두 가장 마지막에 추가하는 것을 권장함

 

추가적으로 위와 같은 이유로 두 라이브러리 사용시에는 어뎁터 추가 순서를 습관적으로 최후순위로 하는 것이 좋다. 

(물론 두 라이브러리 모두 Retrofit 의 Factory 추상클래스에서 응답/요청 컨버터만 구현하고 나머지는 null 을 반환하기에 나머지 컨버터를 구현하는 라이브러리에 대해서는 그보다 아래 위치해도 문제가 없다)

 

코드상에서 확인하고 싶은 니즈를 느끼신다면 

Retrofit의 `Converter` interface 와 각 라이브러리에 해당 인터페이스의 구현체 + Retrofit 의 nextResponseBodyConverter 함수를 확인해보자.

 

이유를 찾는라 라이브러리 코드를 헤매고 헤맸지만 막상 찾고나니 너무 쀼듯 ㅎㅎ

 

마이그레이션 완료: 잘못된 응답에 대해서 데이터를 받는 시점에 에러를 터뜨릴 수 있게되었다. 다시말해 에러 발생 시점을 명확히 할 수 있게 되었다!!

 

참고

https://m1nzi.tistory.com/11

 

Kotlinx Serialization의 JsonBuilder Properties 알아보기

프로젝트에서 서버 통신 후 받은 Response를 문자열(String)으로 웹뷰에 전달해야하는 일이 있어서 kotlinx-serialization의 StringFormat.encodeToString(value: T): String을 사용했다. 그런데 서버에서 전달된 값이

m1nzi.tistory.com

https://medium.com/livefront/migration-gson-to-kotlinx-serialization-ff43cd03631a

 

Migration: Gson to Kotlinx.Serialization

There are plenty of apps out there that are old enough to have been written in Java originally, even if they aren’t anymore. This means…

medium.com

https://tourspace.tistory.com/357

 

[Kotlinx serialization] Json 직렬화/역직렬화 - Fast apply #1

Json을 parsing 하여 Model에 채우거나 Model의 데이터를 Json으로 만들기 위해서는 많은 방법들이 존재합니다. Android에서 사용할 때는 JSONObject를 이용하여 수동(??)으로 직접 한 땀 한 땀 할 수도 있고,

tourspace.tistory.com

https://tourspace.tistory.com/357

 

[Kotlinx serialization] Json 직렬화/역직렬화 - Fast apply #1

Json을 parsing 하여 Model에 채우거나 Model의 데이터를 Json으로 만들기 위해서는 많은 방법들이 존재합니다. Android에서 사용할 때는 JSONObject를 이용하여 수동(??)으로 직접 한 땀 한 땀 할 수도 있고,

tourspace.tistory.com