-
1. Gson
-
내부 구조
-
처리 과정
-
바이트코드/리플렉션 예시
-
특징
-
장점
-
단점
-
2. Moshi
-
내부 구조
-
처리 과정
-
생성된 클래스 예시 (단순화)
-
바이트코드 수준 차이
-
특징
-
장점
-
단점
-
기본 사용법
-
sealed class 처리 예시
-
3. kotlinx.serialization
-
내부 구조
-
처리 과정
-
생성되는 시리얼라이저 예시 (IR 기반)
-
특징
-
장점
-
단점
-
예제
-
sealed class 처리 예시
-
성능 비교 (간략 개요)
-
Retrofit 통합 예시
-
MoshiConverterFactory
-
GsonConverterFactory
-
KotlinxSerializationConverterFactory
-
결론
세 라이브러리는 모두 JSON ↔ Kotlin/Java 객체 간의 직렬화 및 역직렬화를 지원한다. 이들은 REST API 기반의 안드로이드 개발에서 흔히 사용되며, Retrofit과 함께 활용되는 경우가 많다.
1. Gson
내부 구조
Gson은 런타임 리플렉션(Reflection) 기반으로 객체의 필드 정보를 읽어와 JSON과 매핑한다.
- Field 클래스의 get()/set()을 사용
- Getter/Setter 없이도 접근 가능
- 자바 표준 리플렉션 API를 그대로 사용
처리 과정
val user = gson.fromJson(json, User::class.java)
이 구문 실행 시 Gson은 다음과 같은 방식으로 동작한다:
- User::class.java를 기반으로 Class 객체 추출
- Class#getDeclaredFields()로 모든 필드 조회
- 각 필드에 대해 @SerializedName 유무 확인
- JSON 키와 대응되는 필드를 Field#setAccessible(true)로 열고 set()으로 값 설정
바이트코드/리플렉션 예시
Field nameField = User.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(userInstance, "Tom");
즉, Gson은 런타임에 클래스 정보를 탐색하고 값을 설정하기 때문에 다음과 같은 특성이 있다:
- Proguard가 필드 이름을 변경하면 망가짐
- 런타임 비용이 큼 (리플렉션은 느림)
- Kotlin의 val, constructor parameter 기반 구조에 약함
특징
- Google에서 개발한 가장 오래되고 널리 사용된 JSON 라이브러리다.
- Java 기반이므로 Kotlin과 100% 호환되지는 않으며, 리플렉션 기반으로 동작한다.
- @SerializedName을 통해 필드 이름 매핑이 가능하다.
- null 처리, 커스텀 TypeAdapter, Nested Object 처리 등이 잘 되어 있다.
장점
- 가장 많은 자료와 레거시 코드 호환성 확보
- 설정이 매우 간단하다
- 복잡한 구조에도 유연하게 대응 가능
단점
- 성능이 느리다 (리플렉션 사용)
- Proguard 설정 필수
- Kotlin data class에서 null-safe 처리 부족
- sealed class와 같은 Kotlin-specific 타입 지원 부족
data class User(
@SerializedName("user_name") val name: String,
@SerializedName("user_age") val age: Int
)
val json = """{ "user_name": "Tom", "user_age": 30 }"""
val user = Gson().fromJson(json, User::class.java)
println(user.name) // Tom
class DateAdapter : JsonDeserializer<Date> {
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Date {
return SimpleDateFormat("yyyy-MM-dd").parse(json.asString)
}
}
val gson = GsonBuilder()
.registerTypeAdapter(Date::class.java, DateAdapter())
.create()
2. Moshi
내부 구조
Moshi는 기본적으로 Gson과 마찬가지로 리플렉션 기반이지만, @JsonClass(generateAdapter = true)를 명시하면 코드 생성 기반 어댑터(JsonAdapter) 를 만들어 리플렉션 없이 동작할 수 있다.
@JsonClass(generateAdapter = true)
data class User(val name: String, val age: Int)
빌드 시 KAPT(Annotation Processing)를 통해 UserJsonAdapter라는 클래스를 생성한다.
처리 과정
val adapter = moshi.adapter(User::class.java)
adapter.fromJson(json)
- generateAdapter = true일 경우 Moshi는 UserJsonAdapter 클래스를 찾아 사용한다.
- 해당 클래스는 Moshi가 자동으로 생성한 직렬화/역직렬화 로직을 포함한다.
- 그렇지 않으면 Gson처럼 리플렉션을 사용하여 JSON 매핑을 수행한다.
생성된 클래스 예시 (단순화)
public final class UserJsonAdapter extends JsonAdapter<User> {
@Override
public User fromJson(JsonReader reader) {
String name = null;
int age = 0;
...
return new User(name, age);
}
@Override
public void toJson(JsonWriter writer, User user) {
writer.beginObject();
writer.name("name").value(user.name);
writer.name("age").value(user.age);
writer.endObject();
}
}
바이트코드 수준 차이
- 리플렉션 사용 안 함 (Field, Method 접근 없음)
- 모든 직렬화/역직렬화 로직이 클래스 내에 고정됨
- Proguard 영향 없음
특징
- Square사에서 개발한 JSON 라이브러리이며, Gson의 단점을 보완한 구조로 만들어졌다.
- 리플렉션 기반이지만 KotlinJsonAdapterFactory를 추가하면 Kotlin에 최적화된다.
- @Json 어노테이션을 사용한다.
- sealed class, default parameter, nullable, non-null 타입을 안전하게 처리한다.
장점
- Kotlin 친화적 (data class, default 값 지원)
- Proguard 설정 거의 불필요
- 확장성 뛰어남 (Adapter, PolymorphicJsonAdapterFactory)
- sealed class 직렬화 가능 (조건 있음)
단점
- @GeneratedAdapter를 위한 kapt 설정 필요
- kotlin-reflect 사용 시 성능 저하
- 일부 복잡한 구조에서는 어댑터를 직접 만들어야 함
기본 사용법
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "user_name") val name: String,
@Json(name = "user_age") val age: Int
)
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val adapter = moshi.adapter(User::class.java)
val user = adapter.fromJson("""{ "user_name": "Tom", "user_age": 30 }""")
sealed class 처리 예시
sealed class Shape {
@JsonClass(generateAdapter = true)
data class Circle(val radius: Double) : Shape()
@JsonClass(generateAdapter = true)
data class Rectangle(val width: Double, val height: Double) : Shape()
}
val adapter = Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(Shape::class.java, "type")
.withSubtype(Shape.Circle::class.java, "circle")
.withSubtype(Shape.Rectangle::class.java, "rectangle"))
.build()
.adapter(Shape::class.java)
3. kotlinx.serialization
내부 구조
kotlinx.serialization은 Kotlin 컴파일러 플러그인(KSP 또는 IR 기반)을 사용하여 컴파일 타임에 시리얼라이저 클래스를 자동 생성한다.
@Serializable
data class User(val name: String, val age: Int)
- User 클래스에 대해 User$$serializer 라는 컴파일러 생성 클래스가 만들어진다.
- 이 클래스는 KSerializer<User>를 구현하며, JSON 매핑에 필요한 모든 정보와 로직을 포함한다.
처리 과정
val json = Json.decodeFromString<User>(jsonStr)
- Json.decodeFromString()은 내부적으로 User.serializer()를 호출한다.
- User$$serializer가 JSON 파싱 로직을 수행하며, 필드 매핑, 기본값 처리 등을 모두 포함한다.
생성되는 시리얼라이저 예시 (IR 기반)
object User$$serializer : KSerializer<User> {
override val descriptor = buildClassSerialDescriptor("User") {
element<String>("name")
element<Int>("age")
}
override fun deserialize(decoder: Decoder): User {
val input = decoder.beginStructure(descriptor)
var name: String = ""
var age: Int = 0
loop@ while (true) {
when (val index = input.decodeElementIndex(descriptor)) {
0 -> name = input.decodeStringElement(descriptor, 0)
1 -> age = input.decodeIntElement(descriptor, 1)
CompositeDecoder.DECODE_DONE -> break@loop
else -> throw SerializationException("Unexpected index")
}
}
input.endStructure(descriptor)
return User(name, age)
}
...
}
특징
- Kotlin 공식에서 제공하는 Kotlin-native JSON 직렬화 라이브러리다.
- Kotlin 컴파일러 플러그인을 통해 컴파일 타임에 serialization 코드를 생성한다.
- 매우 빠르고 타입 안전성이 뛰어나다.
- @Serializable 어노테이션을 사용하며, 모든 직렬화 기능은 빌드 시 생성된다.
장점
- 성능이 매우 뛰어나다 (코드 생성 기반)
- 리플렉션을 사용하지 않기 때문에 Proguard 설정이 필요 없다
- Kotlin 전용 기능과 궁합이 뛰어나다
- 멀티플랫폼(KMP)에서 사용 가능
단점
- Java와의 호환성이 떨어짐
- 복잡한 타입 구조에서 어노테이션 추가가 많아짐
- 서드파티 라이브러리 통합성은 낮은 편
예제
@Serializable
data class User(
@SerialName("user_name") val name: String,
@SerialName("user_age") val age: Int
)
val json = Json.decodeFromString<User>("""{ "user_name": "Tom", "user_age": 30 }""")
sealed class 처리 예시
@Serializable
sealed class Shape {
@Serializable
@SerialName("circle")
data class Circle(val radius: Double) : Shape()
@Serializable
@SerialName("rectangle")
data class Rectangle(val width: Double, val height: Double) : Shape()
}
val shapeJson = """{ "type": "circle", "radius": 2.5 }"""
val shape = Json { classDiscriminator = "type" }.decodeFromString<Shape>(shapeJson)
성능 비교 (간략 개요)
항목 Gson Moshi kotlinx.serialization
기반 | 리플렉션 | 리플렉션 / 코드 생성 | 코드 생성 |
Kotlin 최적화 | ❌ | ✅ (옵션 필요) | ✅ |
sealed class 지원 | ❌ | ✅ (조건부) | ✅ |
Proguard 설정 | 필수 | 거의 불필요 | 불필요 |
속도 | 느림 | 중간 | 빠름 |
문서/자료 | 많음 | 적당 | 비교적 적음 |
Retrofit 통합 | 매우 쉬움 | 쉬움 | 약간 번거로움 (Converter 필요) |
Retrofit 통합 예시
MoshiConverterFactory
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
GsonConverterFactory
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
KotlinxSerializationConverterFactory
val json = Json { ignoreUnknownKeys = true }
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
결론
- Gson은 지금도 충분히 쓸 수 있지만, Kotlin 중심 개발에서는 Moshi 또는 kotlinx.serialization이 더 적합하다.
- 복잡한 구조와 Kotlin 고유 특성을 많이 사용하는 경우에는 kotlinx.serialization이 가장 강력하다.
- 성능과 Proguard, Kotlin 친화성까지 고려한다면 Moshi가 가장 무난한 선택이다.
'Android > Study' 카테고리의 다른 글
[Android] Retrofit의 내부구조와 동작 알아보기 (0) | 2025.03.26 |
---|---|
[Android] 코루틴 정리 feat. suspend 키워드까지 (0) | 2025.03.19 |
[Android] ViewModel이란 무엇이고 그리고 LifeCycle 까지 (0) | 2025.03.05 |
[Android] 인텐트(Intent) 및 인텐트 필터(Intent Filter) (0) | 2025.02.20 |
[Android] Jetpack Compose의 Recomposition 정리 (0) | 2025.02.20 |
세 라이브러리는 모두 JSON ↔ Kotlin/Java 객체 간의 직렬화 및 역직렬화를 지원한다. 이들은 REST API 기반의 안드로이드 개발에서 흔히 사용되며, Retrofit과 함께 활용되는 경우가 많다.
1. Gson
내부 구조
Gson은 런타임 리플렉션(Reflection) 기반으로 객체의 필드 정보를 읽어와 JSON과 매핑한다.
- Field 클래스의 get()/set()을 사용
- Getter/Setter 없이도 접근 가능
- 자바 표준 리플렉션 API를 그대로 사용
처리 과정
val user = gson.fromJson(json, User::class.java)
이 구문 실행 시 Gson은 다음과 같은 방식으로 동작한다:
- User::class.java를 기반으로 Class 객체 추출
- Class#getDeclaredFields()로 모든 필드 조회
- 각 필드에 대해 @SerializedName 유무 확인
- JSON 키와 대응되는 필드를 Field#setAccessible(true)로 열고 set()으로 값 설정
바이트코드/리플렉션 예시
Field nameField = User.class.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(userInstance, "Tom");
즉, Gson은 런타임에 클래스 정보를 탐색하고 값을 설정하기 때문에 다음과 같은 특성이 있다:
- Proguard가 필드 이름을 변경하면 망가짐
- 런타임 비용이 큼 (리플렉션은 느림)
- Kotlin의 val, constructor parameter 기반 구조에 약함
특징
- Google에서 개발한 가장 오래되고 널리 사용된 JSON 라이브러리다.
- Java 기반이므로 Kotlin과 100% 호환되지는 않으며, 리플렉션 기반으로 동작한다.
- @SerializedName을 통해 필드 이름 매핑이 가능하다.
- null 처리, 커스텀 TypeAdapter, Nested Object 처리 등이 잘 되어 있다.
장점
- 가장 많은 자료와 레거시 코드 호환성 확보
- 설정이 매우 간단하다
- 복잡한 구조에도 유연하게 대응 가능
단점
- 성능이 느리다 (리플렉션 사용)
- Proguard 설정 필수
- Kotlin data class에서 null-safe 처리 부족
- sealed class와 같은 Kotlin-specific 타입 지원 부족
data class User(
@SerializedName("user_name") val name: String,
@SerializedName("user_age") val age: Int
)
val json = """{ "user_name": "Tom", "user_age": 30 }"""
val user = Gson().fromJson(json, User::class.java)
println(user.name) // Tom
class DateAdapter : JsonDeserializer<Date> {
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Date {
return SimpleDateFormat("yyyy-MM-dd").parse(json.asString)
}
}
val gson = GsonBuilder()
.registerTypeAdapter(Date::class.java, DateAdapter())
.create()
2. Moshi
내부 구조
Moshi는 기본적으로 Gson과 마찬가지로 리플렉션 기반이지만, @JsonClass(generateAdapter = true)를 명시하면 코드 생성 기반 어댑터(JsonAdapter) 를 만들어 리플렉션 없이 동작할 수 있다.
@JsonClass(generateAdapter = true)
data class User(val name: String, val age: Int)
빌드 시 KAPT(Annotation Processing)를 통해 UserJsonAdapter라는 클래스를 생성한다.
처리 과정
val adapter = moshi.adapter(User::class.java)
adapter.fromJson(json)
- generateAdapter = true일 경우 Moshi는 UserJsonAdapter 클래스를 찾아 사용한다.
- 해당 클래스는 Moshi가 자동으로 생성한 직렬화/역직렬화 로직을 포함한다.
- 그렇지 않으면 Gson처럼 리플렉션을 사용하여 JSON 매핑을 수행한다.
생성된 클래스 예시 (단순화)
public final class UserJsonAdapter extends JsonAdapter<User> {
@Override
public User fromJson(JsonReader reader) {
String name = null;
int age = 0;
...
return new User(name, age);
}
@Override
public void toJson(JsonWriter writer, User user) {
writer.beginObject();
writer.name("name").value(user.name);
writer.name("age").value(user.age);
writer.endObject();
}
}
바이트코드 수준 차이
- 리플렉션 사용 안 함 (Field, Method 접근 없음)
- 모든 직렬화/역직렬화 로직이 클래스 내에 고정됨
- Proguard 영향 없음
특징
- Square사에서 개발한 JSON 라이브러리이며, Gson의 단점을 보완한 구조로 만들어졌다.
- 리플렉션 기반이지만 KotlinJsonAdapterFactory를 추가하면 Kotlin에 최적화된다.
- @Json 어노테이션을 사용한다.
- sealed class, default parameter, nullable, non-null 타입을 안전하게 처리한다.
장점
- Kotlin 친화적 (data class, default 값 지원)
- Proguard 설정 거의 불필요
- 확장성 뛰어남 (Adapter, PolymorphicJsonAdapterFactory)
- sealed class 직렬화 가능 (조건 있음)
단점
- @GeneratedAdapter를 위한 kapt 설정 필요
- kotlin-reflect 사용 시 성능 저하
- 일부 복잡한 구조에서는 어댑터를 직접 만들어야 함
기본 사용법
@JsonClass(generateAdapter = true)
data class User(
@Json(name = "user_name") val name: String,
@Json(name = "user_age") val age: Int
)
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val adapter = moshi.adapter(User::class.java)
val user = adapter.fromJson("""{ "user_name": "Tom", "user_age": 30 }""")
sealed class 처리 예시
sealed class Shape {
@JsonClass(generateAdapter = true)
data class Circle(val radius: Double) : Shape()
@JsonClass(generateAdapter = true)
data class Rectangle(val width: Double, val height: Double) : Shape()
}
val adapter = Moshi.Builder()
.add(PolymorphicJsonAdapterFactory.of(Shape::class.java, "type")
.withSubtype(Shape.Circle::class.java, "circle")
.withSubtype(Shape.Rectangle::class.java, "rectangle"))
.build()
.adapter(Shape::class.java)
3. kotlinx.serialization
내부 구조
kotlinx.serialization은 Kotlin 컴파일러 플러그인(KSP 또는 IR 기반)을 사용하여 컴파일 타임에 시리얼라이저 클래스를 자동 생성한다.
@Serializable
data class User(val name: String, val age: Int)
- User 클래스에 대해 User$$serializer 라는 컴파일러 생성 클래스가 만들어진다.
- 이 클래스는 KSerializer<User>를 구현하며, JSON 매핑에 필요한 모든 정보와 로직을 포함한다.
처리 과정
val json = Json.decodeFromString<User>(jsonStr)
- Json.decodeFromString()은 내부적으로 User.serializer()를 호출한다.
- User$$serializer가 JSON 파싱 로직을 수행하며, 필드 매핑, 기본값 처리 등을 모두 포함한다.
생성되는 시리얼라이저 예시 (IR 기반)
object User$$serializer : KSerializer<User> {
override val descriptor = buildClassSerialDescriptor("User") {
element<String>("name")
element<Int>("age")
}
override fun deserialize(decoder: Decoder): User {
val input = decoder.beginStructure(descriptor)
var name: String = ""
var age: Int = 0
loop@ while (true) {
when (val index = input.decodeElementIndex(descriptor)) {
0 -> name = input.decodeStringElement(descriptor, 0)
1 -> age = input.decodeIntElement(descriptor, 1)
CompositeDecoder.DECODE_DONE -> break@loop
else -> throw SerializationException("Unexpected index")
}
}
input.endStructure(descriptor)
return User(name, age)
}
...
}
특징
- Kotlin 공식에서 제공하는 Kotlin-native JSON 직렬화 라이브러리다.
- Kotlin 컴파일러 플러그인을 통해 컴파일 타임에 serialization 코드를 생성한다.
- 매우 빠르고 타입 안전성이 뛰어나다.
- @Serializable 어노테이션을 사용하며, 모든 직렬화 기능은 빌드 시 생성된다.
장점
- 성능이 매우 뛰어나다 (코드 생성 기반)
- 리플렉션을 사용하지 않기 때문에 Proguard 설정이 필요 없다
- Kotlin 전용 기능과 궁합이 뛰어나다
- 멀티플랫폼(KMP)에서 사용 가능
단점
- Java와의 호환성이 떨어짐
- 복잡한 타입 구조에서 어노테이션 추가가 많아짐
- 서드파티 라이브러리 통합성은 낮은 편
예제
@Serializable
data class User(
@SerialName("user_name") val name: String,
@SerialName("user_age") val age: Int
)
val json = Json.decodeFromString<User>("""{ "user_name": "Tom", "user_age": 30 }""")
sealed class 처리 예시
@Serializable
sealed class Shape {
@Serializable
@SerialName("circle")
data class Circle(val radius: Double) : Shape()
@Serializable
@SerialName("rectangle")
data class Rectangle(val width: Double, val height: Double) : Shape()
}
val shapeJson = """{ "type": "circle", "radius": 2.5 }"""
val shape = Json { classDiscriminator = "type" }.decodeFromString<Shape>(shapeJson)
성능 비교 (간략 개요)
항목 Gson Moshi kotlinx.serialization
기반 | 리플렉션 | 리플렉션 / 코드 생성 | 코드 생성 |
Kotlin 최적화 | ❌ | ✅ (옵션 필요) | ✅ |
sealed class 지원 | ❌ | ✅ (조건부) | ✅ |
Proguard 설정 | 필수 | 거의 불필요 | 불필요 |
속도 | 느림 | 중간 | 빠름 |
문서/자료 | 많음 | 적당 | 비교적 적음 |
Retrofit 통합 | 매우 쉬움 | 쉬움 | 약간 번거로움 (Converter 필요) |
Retrofit 통합 예시
MoshiConverterFactory
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
GsonConverterFactory
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
KotlinxSerializationConverterFactory
val json = Json { ignoreUnknownKeys = true }
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
결론
- Gson은 지금도 충분히 쓸 수 있지만, Kotlin 중심 개발에서는 Moshi 또는 kotlinx.serialization이 더 적합하다.
- 복잡한 구조와 Kotlin 고유 특성을 많이 사용하는 경우에는 kotlinx.serialization이 가장 강력하다.
- 성능과 Proguard, Kotlin 친화성까지 고려한다면 Moshi가 가장 무난한 선택이다.
'Android > Study' 카테고리의 다른 글
[Android] Retrofit의 내부구조와 동작 알아보기 (0) | 2025.03.26 |
---|---|
[Android] 코루틴 정리 feat. suspend 키워드까지 (0) | 2025.03.19 |
[Android] ViewModel이란 무엇이고 그리고 LifeCycle 까지 (0) | 2025.03.05 |
[Android] 인텐트(Intent) 및 인텐트 필터(Intent Filter) (0) | 2025.02.20 |
[Android] Jetpack Compose의 Recomposition 정리 (0) | 2025.02.20 |