
OkHttp, retrofit을 사용하면 안드로이드에서 Json파일을 직렬화 해서 사용해야한다.
그럴때 컨버트 라이브러리로 주로 Gson을 사용했는데, 요즘 잘 안쓴다고 한다.
20년도에 Jake Wharton이 말하길 개발자 3명중 2명은 Gson은 deprecate되었다고 한다.
현재는 Moshi를 사용한다고 하는데, 그 이유는 뭐고 Gson은 왜 안쓰일까?
서로의 장단점을 정리해보자
❌ 코틀린 친화적이지 않다, null-safety같은 코틀린의 특징을 준수하지 않고 업데이트 또한 더딘 편이다.
가장 최신 버전이 2.10.1버전 인데, 23.01.07에 나오고 이후 업데이트가 되지 않았다.
https://github.com/google/gson
❌ 메소드 수 또한 Moshi에 비해 거의 2배정도 차이 난다. (20년도 기준)
그렇기 때문에 인터페이스가 복잡해지고, 성능 등 문제가 발생한다.
❌ 용량 또한 차이가 나는대, Gson은 APK에 277KB (2.10.1 버전 기준), Moshi는 158KB ,코틀린 확장시 +19.8KB 177.8kb가 된다(1.15.1 버전 기준).
https://central.sonatype.com/
❌ Gson은 리플렉션을 사용하여 JSON 문자열을 직렬화/역직렬화할 수만 있다.
이게 왜 문제 냐면, 리플렉션은 런타임에 클래스의 메타데이터를 분석하기 때문에, 컴파일 시간에 코드를 최적화하는 것보다 성능이 떨어진다.
또한 프로가드 같은 코드 축소 및 최적화가 힘들며, 보안또한 문제가된다.
Moshi같은경우 Kotlin과 함께 사용할 때 코드 생성방식을 제공한다.
❌ Gson은 필드에 대한 기본값을 지원하지 않는다. 응답 Json 문자열에 필드가 누락되고 해당 기본값을 null이 아닌 다른 값으로 설정해도 null이 된다.
✔️JSON에서 데이터를 읽을 때, 예를 들어 색상 코드를 숫자로 바꿔야 하는 등의 특별한 처리가 필요한 경우, 그 처리 방식을 정의하는 '맞춤형 태그'를 만들어 코드에 붙일 수 있다.
// 커스텀 어노테이션 정의 예
annotation class FromHexColor
// 사용 예
data class ThemeSettings(
@FromHexColor val backgroundColor: Int // JSON에서 "#FFFFFF" 같은 문자열을 Int로 자동 변환
)
✔️덜 반복되는 코드를 작성할 수 있도록 하는 깔끔한 기능들을 제공한다.
지연 로딩 어댑터를 사용할 수 있는데,
지연 로딩 어댑터는 애플리케이션의 성능을 최적화하고, 불필요한 메모리 사용을 줄이는 데 도움을 준다.
예를 들어, 애플리케이션에서 사용자의 프로필 데이터를 JSON형식으로 서버에서 받아오지만, 사용자가 프로필 페이지를 열 때까지 프로필 데이터를 파싱할 필요가 없는 경우, 사용자가 실제로 프로필페이지를 열 때까지 데이터 파싱을 연기할 수 있다.
// Moshi 인스턴스 생성
val moshi = Moshi.Builder().build()
// 지연 로딩 어댑터 생성
val adapter = moshi.adapter(Profile::class.java).lazy()
val profile = adapter.fromJson(data)
✔️오류메시지또한 장점이다.
Moshi는 오류 발생 시 더 이해하기 쉬운 직렬화 실패 메시지를 제공한다.
앱이 배포된 후 이상한 스택 트레이스를 받았을 때 원인을 파악하기 더 쉽게 해준다.
✔️newBuilder() API를 통해 기존 어댑터를 기반으로 새 어댑터를 생성할 수 있다.
OkHttp또는 Okio빌더와 유사하며, 별도의 어댑터를 생성함으로 하나의 큰 어댑터가 수천개의 모델을 파싱하는걸 방지할 수 있다.
✔️Moshi는 다형성 데이터 타입을 내장 지원하며, 알려지지 않은 타입에 대해서도 폴백 지원을 한다.
다형성 데이터 타입 지원은 서로 다른 타입의 객체를 동일한 상위클래스나 인터페이스로 처리할 수 있게 해주는 기능이다.
예를 들어, Animal 이라는 인터페이스가 있고 Dog와 Cat이라는 두개의 구현체가 있을때, Moshi는 Json 데이터를 보고 어떤 타입(Dog 혹은 Cat)으로 객체를 생성할지 결정할 수 있다.
✔️폴백지원은 Moshi가 처리할 수 없는 타입이 나타났을 때 대체할 수 있는 기본 동작이나 객체를 제공하는 기능이다.
예를 들어, Animal 타입의 JSON 배열을 파싱하는 중에 type이 Dog나 Cat이 아닌 Bird로 설정된 객체를 발견했을때, bird에 대한 처리 로직이 없다면 폴백 객체로 정의된 UnknwnAnimal클래스의 인스턴스를 생성할 수 있다.
이렇게 하면 프로그램이 예외 없이 계속 실행되게 된다
✔️Moshi는 코틀린용 Code-gen 어댑터가 있다.
주석을 사용해 직렬화/역직렬화 과정을 더욱 빠르게 만들 수 있다. Gson이 사용하는 리플렉션 방식을 우회하는 방법인데, 런타임에서 분석하는 리플렉션 방식과 달리
컴파일 시점에서 확인하기 때문에 처리속도가 빠르고 타입안전성이 높아지며, 프로가드 같은 도구로 축소 및 최적화하기 용이 하다.
Moshi는 Gson보다 훨씬 빠르고 메모리를 덜 사용한다.
Okio를 사용하기 때문인데, retrofit또한 내부적으로 Okio를 사용하고 있다.
그렇게 되면 Moshi와 Retrofit는 사용하는 버퍼를 공유하게되고, 네트워크 호출과 응답을 직렬화 하는 동안 메모리 소비가 훨씬 줄어든다.
💻사용 방법
/*Moshi*/
def moshiVersion = "1.15.1"
implementation("com.squareup.moshi:moshi:$moshiVersion")
kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion")
implementation("com.squareup.retrofit2:converter-moshi:2.11.0")
@JsonClass(generateAdapter = true)
data class Animal(
@Json(name = "dog")
val dog: String,
@Json(name = "cat")
val cat: String,
@Json(name = "UnknwnAnimal")
val UnknwnAnimal: String,
)
@Singleton
@Provides
fun providesMoshi() = Moshi.Builder().build()
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient, mosh: Moshi) = Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create(mosh))
.baseUrl(BASE_ENDPOINT)
.build()
This is a great breakdown of Moshi vs Gson! I've been using Gson for a while, but the limitations you mentioned, like slow updates and lack of Kotlin support, make Moshi sound like a much better Slope Game option. The custom tag functionality and lazy loading adapters seem particularly helpful.