-
Serialization 라이브러리 마이그레이션 과정(Gson -> Kotlinx-Serialization)Android & Kotlin 2023. 12. 23. 11:42
개요
- 위 글에서는 Gson 에서 Kotlinx-Serialization 으로 의 마이그에션 도중 겪은 문제 및 그 과정에서 얻게된 정보들을 정리합니다.
- 마이그레이션 배경에 대해서는 이전 글을 참고해주세요
📌 마이그레이션 순서
- 라이브러리 선언 (각 모듈 별로 선언해야함)
- 모델 변경 (어노테이션 수정), Adapter (Serializer) 정의
- retrofit converter 정의 및 추가
- 통신 이외 부분에서도 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 함수를 확인해보자.
이유를 찾는라 라이브러리 코드를 헤매고 헤맸지만 막상 찾고나니 너무 쀼듯 ㅎㅎ마이그레이션 완료: 잘못된 응답에 대해서 데이터를 받는 시점에 에러를 터뜨릴 수 있게되었다. 다시말해 에러 발생 시점을 명확히 할 수 있게 되었다!!
참고
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
'Android & Kotlin' 카테고리의 다른 글
직렬화 라이브러리 마이그레이션 이유(Gson 에서 Kotlinx-Serialization) (1) 2023.12.22 Hilt 관련 리펙토링 기록 (@binds @Provides 차이 , UsecaseModule) (0) 2023.01.08 DialogFragment 오류 정리 (Radius , 크기 지정) (0) 2022.11.28 By 키워드를 이용한 Shared Preference (0) 2022.11.18 Android 13 알아보기 (Target SDK 33 으로 변경) (0) 2022.11.11