Moshi: 핵심 개념, Kotlin용 JSON 라이브러리 및 작동 방식

저자: IT Sectr 게시일: 2026-03-15 읽는 시간: 8 분

Moshi는 Square가 Gson의 한계를 고려하여 Kotlin과 Android를 위해 특히 제작한 현대적인 JSON 라이브러리입니다. Kotlin의 null 안전성과 완전히 호환되며, 컴파일 시점에 코드를 생성하고 리플렉션을 사용하지 않아 성능과 신뢰성이 향상됩니다. Square Moshi, 2024에 따르면, Moshi는 예측 가능한 직렬화를 제공하고 모든 데이터 유형에 대한 사용자 정의 어딕터를 지원합니다.

주요 포인트

  • Moshi — 리플렉션 없이 Kotlin과 Android를 위한 Square의 JSON 라이브러리
  • Kotlin 어딕터 — data class, 기본값 및 null 안전성에 대한 내장 지원
  • @Json — 필드 이름 설정 및 특성 무시를 위한 어노테이션
  • 어딕터 — @ToJson과 @FromJson을 통한 사용자 정의 직렬화 로직
  • 코드 생성 — Moshi는 kapt 또는 KSP를 통해 컴파일 시점에 어딕터를 생성

Moshi란?

Moshi는 Square(OkHttp와 Retrofit의 저자)가 만든 JVM, Android 및 Kotlin Multiplatform용 JSON 라이브러리입니다. Gson과 달리 Moshi는 리플렉션에 의존하지 않습니다 — 어딕터는 @JsonClass(generateAdapter = true) 어노테이션을 통해 컴파일 시점에 생성됩니다. 이를 통해 Moshi는 Kotlin 고유 구조를 다룰 때 더 빠르고 안전하며 예측 가능합니다.

철학과 장점

Moshi가 전기 기술과 다른 주요 차이점은 리플렉션을 거부한 것입니다. 리플렉션은 Gson이 준비 없이 어떤 클래스와도 작동할 수 있게 하지만, 그 대가로 초기화가 느리고 컴파일러의 최적화가 불가능하며 런타임 오류의 위험이 있습니다. Moshi는 코드 생성을 위해 클래스의 명시적 선언이 필요하지만, 그 대가로 수동 코드의 속도와 컴파일 시점의 완전한 타입 안전성을 제공합니다.

kotlin
// build.gradle에 Moshi 추가
dependencies {
    implementation "com.squareup.moshi:moshi:1.15.0"
    implementation "com.squareup.moshi:moshi-kotlin:1.15.0"
    kapt "com.squareup.moshi:moshi-kotlin-codegen:1.15.0"
}

// 코드 생성을 사용한 간단한 모델
@JsonClass(generateAdapter = true)
data class User(
    @Json(name = "user_id")
    val id: Int,
    val name: String,
    val email: String,
    val avatar: String? = null
)

// 사용법
val moshi = Moshi.Builder()
    .build()
val jsonAdapter = moshi.adapter(User::class.java)

설치 및 구성

Moshi를 사용하려면 build.gradle에 의존성을 추가하고 모델을 어노테이트해야 합니다. Moshi.Builder가 입구 점으로 작용하며, 이를 통해 표준 유형에 대한 내장 어딕터, 사용자 정의 어딕터가 추가되고 라이브러리의 동작이 구성됩니다. Moshi는 Date, Enum, Collection 및 Map에 대한 어딕터를 기본으로 지원하지만, Kotlin 클래스에는 moshi-kotlin 모듈이 필요합니다. Gson과 달리 Moshi는 기본적으로 Kotlin 클래스에 리플렉션을 사용하지 않습니다 — 이를 위해 KotlinJsonAdapterFactory가 연결되며, 코드 생성이 사용되지 않거나 클래스가 @JsonClass로 어노테이트되지 않은 경우 보안으로 작동합니다. 이 접근 방식은 개발자가 각 특정 클래스에 대해 코드 생성의 성능과 리플렉션의 유연성 간에 명시적으로 선택할 수 있도록 보장합니다.

Moshi 생성 및 어딕터 추가

Builder를 통해 Moshi를 빌드한 후, 개발자는 Moshi 인스턴스를 홝봉하고 필요한 클래스에 대한 어딕터를 요청합니다. JsonAdapter는 toJson()을 통한 직렬화와 fromJson()을 통한 역직렬화를 수행하는 중심 객체입니다. 클래스가 @JsonClass(generateAdapter = true)로 어노테이트된 경우 Moshi는 자동으로 생성된 어딕터를 사용하고, 그렇지 않은 경우 보안으로 리플렉티브 KotlinJsonAdapterFactory를 적용합니다. 이 접근 방식은 어떤 규모와 복잡성의 프로젝트에도 코드 생성의 속도와 리플렉티브 메카니즘의 유연성을 결합합니다. Moshi는 작은 애플리케이션부터 수백 개의 데이터 모델을 가진 대규모 기업 프로젝트까지 적합합니다.

kotlin
// KotlinJsonAdapterFactory로 Moshi 구성
val moshi = Moshi.Builder()
    .add(KotlinJsonAdapterFactory())
    .add(LocalDateAdapter())
    .build()

// 어딕터 사용
val adapter = moshi.adapter(User::class.java)

// 직렬화
val user = User(1, "Alice", "alice@test.com")
val json = adapter.toJson(user)

// 역직렬화
val jsonString = """{"user_id":2,"name":"Bob","email":"bob@test.com"}"""
val parsedUser = adapter.fromJson(jsonString)

// 리스트 작업
val listAdapter = moshi.adapter(
    Types.newParameterizedType(
        List::class.java,
        User::class.java
    )
)

어노테이션과 어딕터

Moshi는 직렬화를 구성하고 사용자 정의 유형을 지원하기 위해 어노테이션을 사용합니다. @Json(name = "...")는 필드에 대한 JSON 키를 설정합니다. @Transient는 필드를 직렬화에서 제외합니다. @JsonClass(generateAdapter = true)는 코드 생성을 활성화합니다. 사용자 정의 로직을 위해 Moshi는 @ToJson과 @FromJson 어노테이션을 제공하며, 이들을 별도의 어딕터 클래스에 배치할 수 있습니다.

@Json과 사용자 정의 어딕터

@Json 어노테이션은 Gson의 @SerializedName을 대체하고 비슷하게 작동합니다. kotlinName 필드가 "kotlin_name" JSON 키와 연결됩니다. Moshi가 기본적으로 직렬화할 수 없는 유형(예: LocalDate)에 대해 개발자는 @ToJson과 @FromJson 메소드를 가진 클래스를 만듭니다. 어딕터는 Moshi.Builder.add()를 통해 등록되며 전역적으로 또는 특정 유형에 적용됩니다. Moshi는 실링된 클래스와 @JsonClass를 통한 명시적 구별자로 다형 직렬화를 지원하여, 수동 필드 검사 없이 JSON에서 타입 계층을 다룰 수 있게 합니다. 역직렬화 시 Moshi는 기본적으로 알 수 없는 JSON 키를 무시하여, 클라이언트 코드를 변경하지 않고 서버 쪽에 새 필드를 추가할 때 후향 호환성을 보장합니다. 디버깅을 위해 failOnUnknown을 통해 엄격 모드를 활성화할 수 있으며, 알 수 없는 키가 발견되면 예외를 발생시킵니다.

kotlin
// LocalDate용 사용자 정의 어딕터
class LocalDateAdapter {

    @ToJson
    fun toJson(date: LocalDate): String {
        return date.format(DateTimeFormatter.ISO_LOCAL_DATE)
    }

    @FromJson
    fun fromJson(dateString: String): LocalDate {
        return LocalDate.parse(dateString)
    }
}

// Moshi 어노테이션이 있는 모델
@JsonClass(generateAdapter = true)
data class Event(
    @Json(name = "event_id")
    val id: Int,

    @Json(name = "event_date")
    val date: LocalDate,

    @Transient
    val localCache: String? = null
)

// 어딕터 등록
val moshi = Moshi.Builder()
    .add(LocalDateAdapter())
    .add(KotlinJsonAdapterFactory())
    .build()

Moshi vs Gson

Moshi와 Gson의 비교는 Android 프로젝트용 JSON 라이브러리를 선택할 때 자주 나타나는 질문입니다. Moshi는 코드 생성, null 안전성 및 속도 때문에 현대 Kotlin 개발에서 우세합니다. Gson은 Java 프로젝트, 레거시 코드 및 최소한의 구성이 중요한 상황에서 여전히 유용합니다. 차이는 대량 데이터와 복잡한 모델에서 두드러집니다.

성능 및 안전성

성능 테스트에 따르면, 코드 생성을 사용한 Moshi는 직렬화 및 역직렬화 작업에서 Gson보다 2–5배 빠릅니다. Moshi의 주요 장점은 Kotlin의 null 안전성을 올바르게 처리한다는 점입니다. JSON에 필드가 없고 모델이 기본값 없이 non-null을 선언한 경우, Moshi는 역직렬화 시 예외를 발생시켜 숨겨진 오류를 방지합니다.

특성GsonMoshi
메카니즘리플렉션코드 생성 / 리플렉션
Null 안전성고려하지 않음완전한 Kotlin 지원
속도중간높음
기본값지원하지 않음지원함
Kotlin Multiplatform없음있음
라이브러리 크기~240 Kb~150 Kb

Moshi와 Gson 간의 선택은 프로젝트 문맥에 따락니다. 새 Kotlin 프로젝트는 타입 안전성과 성능 때문에 Moshi의 혜택을 받습니다. Gson은 Java 코드 지원, 동적 JSON 구조, 또는 속도보다 설정의 간편성이 더 중요한 상황에서 여전히 합리적인 선택입니다. Kotlin Multiplatform의 경우, Moshi는 이 플랫폼을 지원하는 두 옵션 중 유일한 라이브러리입니다.

Gson에서 Moshi로 마이그레이션할 때 주요 변경 사항은 어노테이션과 어딕터에 관련됩니다. Gson의 @SerializedName은 @Json(name = "...")으로 대체되고, 사용자 정의 JsonSerializer/JsonDeserializer는 @ToJson/@FromJson 쌍으로 대체됩니다. 기본값과 nullable 필드가 있는 모델의 경우 Moshi는 더 예측 가능하게 동작합니다. JSON에 기본값 없는 non-null 필드가 없는 경우 Moshi는 JsonDataException을 발생시켜 숨겨진 NPE를 방지합니다. MoshiConverterFactory를 통한 Retrofit의 연계는 단일 의존성으로 추가되며 네트워크 계층 아키텍처를 변경할 필요가 없습니다. ProGuard 또는 R8을 통한 추적 방지를 위해 @JsonClass로 어노테이트된 클래스와 생성된 어딕터를 보존하는 규칙을 추가해야 하며, 그렇지 않으면 릴리스 빌드에서 직렬화가 작동하지 않습니다. 요약하면, 성능과 타입 안전성이 중요한 새 Kotlin 프로젝트에서 Gson에서 Moshi로의 마이그레이션이 정당화됩니다.

kotlin
// 직렬화 비교: Gson vs Moshi
data class Sample(
    val name: String,
    val count: Int,
    val tags: List<String> = listOf()
)

// Gson: 리플렉션으로 동작
val gson = Gson()
val fromGson = gson.fromJson("""{"name":"test"}""",
    Sample::class.java)
// count = 0(기본), 하지만 null 안전성이 확인되지 않음

// Moshi: 어딕터 필요, null 안전성이 명시적
@JsonClass(generateAdapter = true)
data class SampleMoshi(
    val name: String,
    val count: Int,
    val tags: List<String> = listOf()
)

자주 묻는 질문

Android에서 Moshi란 무엇인가요?

Moshi는 리플렉션 대신 코드 생성을 사용하는 Square의 Kotlin 및 Android용 JSON 라이브러리입니다. 높은 성능, Kotlin null 안전성의 올바른 처리 및 Kotlin Multiplatform과의 호환성을 제공합니다.

Moshi가 Gson보다 어떻게 더 좋은가요?

Moshi는 속도(코드 생성으로 2–5배 빠름), 안전성(Kotlin null 어노테이션 준수) 및 크기(~90 Kb 작음)에서 Gson을 능가합니다. Moshi는 Kotlin Multiplatform과 data class의 기본값도 지원합니다.

Moshi에서 @JsonClass 어노테이션은 어떻게 작동하나요?

@JsonClass(generateAdapter = true)는 주어진 클래스에 대한 어딕터를 컴파일 시점에 생성하도록 Moshi에 지시합니다. 생성된 어딕터는 리플렉션 없이 직접 직렬화를 수행하여 최대 성능을 제공합니다.

사용자 정의 Moshi 어딕터를 만드는 방법은?

@ToJson(직렬화) 및 @FromJson(역직렬화)으로 어노테이트된 메소드를 가진 클래스를 만드세요. Moshi.Builder.add()를 통해 인스턴스를 등록하세요. Moshi는 해당 유형을 처리할 때 자동으로 어딕터를 찾아 적용합니다.

Moshi는 Kotlin Multiplatform을 지원하나요?

네, Moshi는 버전 1.13.0부터 Kotlin Multiplatform을 지원합니다. 이를 통해 Moshi는 KMP 프로젝트를 위한 유일한 인기 JSON 해결책이 되며, 모든 대상 플랫폼에서 공통 직렬화 코드를 사용할 수 있습니다.

요약

  • Moshi — 리플렉션 대신 코드 생성을 사용하는 Square의 현대적인 JSON 라이브러리
  • @JsonClass — 수동 코드의 속도를 제공하는 어딕터 생성 어노테이션
  • @Json — JSON 키 구성, @Transient — 직렬화에서 필드 제외
  • @ToJson과 @FromJson — 모든 유형에 대한 사용자 정의 어딕터의 간단한 API
  • Null 안전성 — Moshi는 Kotlin 어노테이션을 준수하고 일치하지 않으면 예외 발생
  • 성능 — 직렬화 및 역직렬화 작업에서 Gson보다 2–5배 빠름
  • Kotlin Multiplatform — 보편 직렬화 코드를 위한 KMP 지원

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기