Kotlin Ready

JSON → Kotlin Data Class: Android·KMP 실무 가이드

Android와 KMP 프로젝트에서 JSON 응답을 null-safe data class로 변환하는 방법을 소개합니다. 직렬화 라이브러리 선택, nullability, sealed 구조까지 한 번에 점검하세요.

변환기로 이동 Null-safety · Serialization · Sealed type

왜 Kotlin data class로 변환할까?

Kotlin은 기본적으로 null-safety를 제공하지만, JSON 스키마가 느슨하면 여전히 런타임 크래시가 발생할 수 있습니다. JSON2Class를 사용하면 샘플 응답을 붙여 넣는 즉시 data class를 생성하고, nullable 여부를 통제해 API 계약을 분명히 할 수 있습니다.

Android, Ktor, Spring, 서버리스 환경 모두에서 재사용 가능하며, `copy`, `equals`, `hashCode` 등 data class가 제공하는 기능을 그대로 활용할 수 있습니다. 필드 경로를 Markdown으로 내보내면, 프런트엔드/백엔드/QA가 동일한 자료를 참조하게 됩니다.

주요 사용 사례

  1. 모바일 API 통합: Retrofit/OkHttp 응답을 `kotlinx.serialization` 혹은 Moshi/Gson으로 파싱할 때 타입을 고정.
  2. 멀티플랫폼 공유: KMM에서 공용 data class로 변환해 iOS/Android 양쪽에서 동일한 모델을 사용.
  3. 테스트 픽스처: Fake API나 로컬 JSON 파일을 data class에 매핑해 UI 상태를 빠르게 검증.
  4. 계약 검증: GraphQL/REST 스키마 변경 시 생성된 Markdown 표를 리뷰에 첨부해 영향도를 체크.

JSON → Kotlin 변환 예시

다음 JSON을 변환하면, nullable과 비 nullable 필드가 구분된 data class를 얻을 수 있습니다. 배열은 `List`, 숫자는 Double/Int로 추론되며, 중첩 객체는 별도 data class로 나뉩니다.

{
  "id": 401,
  "title": "Flight status",
  "delayed": false,
  "tags": ["airport", "notice"],
  "meta": {
    "gate": "A12",
    "etaMinutes": 25
  }
}
import kotlinx.serialization.Serializable

@Serializable
data class Notice(
  val id: Int,
  val title: String,
  val delayed: Boolean?,
  val tags: List?,
  val meta: Meta?
) {
  @Serializable
data class Meta(
    val gate: String?,
    val etaMinutes: Int?
  )
}

직렬화 라이브러리를 선택하지 않은 경우 `@Serializable` 어노테이션은 제거해도 됩니다. Moshi/Gson을 쓴다면 `@JsonClass(generateAdapter = true)` 또는 `@SerializedName`을 추가해 이름 매핑을 고정하세요.

타입 매핑과 null-safety 전략

  • 문자열은 `String`, 정수는 `Int`, 실수는 `Double`, 불리언은 `Boolean`으로 매핑됩니다.
  • 필드가 응답마다 사라질 수 있다면 `?`를 붙여 nullable로, 항상 온다면 non-null로 고정합니다.
  • 날짜/시간 문자열은 팀 규칙에 따라 `Instant`, `LocalDate`, `LocalDateTime` 중 하나로 교체하고, 직렬화 포맷을 지정합니다.
  • 동적 키를 가진 JSON은 `Map`로 받아 처리하고, 필요하면 key set을 제한하는 래퍼 클래스를 둡니다.

`sealed interface`나 `sealed class`를 사용하면 타입이 분기되는 응답(예: 상태별 payload)을 안전하게 모델링할 수 있습니다. 변환기로 기본 data class를 만든 뒤, 분기별 하위 클래스를 나눠 구현하세요.

직렬화 라이브러리별 체크리스트

kotlinx.serialization: multiplatform에 적합하며, 모든 클래스에 `@Serializable` 추가. 필드 이름이 JSON과 다르면 `@SerialName("json_name")`을 붙입니다.

Moshi: codegen을 사용한다면 `@JsonClass(generateAdapter = true)`로 어댑터 생성. Kotlin-reflect를 피하고 싶다면 kapt 또는 ksp 기반 codegen을 권장합니다.

Gson: 가장 관대하지만, null 처리가 느슨할 수 있습니다. 필요한 필드에는 `@Expose`를 붙여 직렬화 대상을 명확히 합니다.

자주 겪는 문제와 해결 팁

  • Optional 컬렉션: 빈 배열과 null 배열을 구분해야 한다면 `List?` 대신 `List`으로 고정하고, 빈 배열을 기본값으로 사용합니다.
  • Enum 매핑: 서버에서 문자열 enum을 보낸다면 Kotlin `enum class`를 추가하고 `@SerialName`으로 연결하세요.
  • 대용량 리스트: 페이징 응답은 `items` 외에 `cursor`, `hasNext` 필드를 포함하므로, 별도 래퍼 data class를 만들어 재사용합니다.
  • 보안/개인정보: 민감한 JSON은 샘플에서 가명화한 뒤 변환하고, 실제 키 이름이 노출되지 않도록 주의합니다.

코루틴 기반 호출에서 예외를 명확히 처리하려면, 변환된 data class를 `Result` 래퍼와 함께 사용하거나, sealed 결과 타입을 정의해 성공/실패를 구분하세요.

KMP와 Android에서의 적용 흐름

멀티플랫폼에서는 공용 모듈에 data class를 두고, 플랫폼별 직렬화 스택을 주입하는 방식이 일반적입니다. iOS에서는 Kotlin/Native가 생성한 Swift 인터페이스를 활용해 동일한 모델을 사용할 수 있습니다. Android에서는 Retrofit + kotlinx.serialization, 서버에서는 Ktor를 사용하는 식으로, 하나의 모델을 여러 환경에서 재사용해 보세요.

UI 레이어에서는 data class를 ViewState로 바로 쓰기보다는, 필요한 필드만 가진 UI 모델로 매핑해 사용하는 편이 테스트와 유지보수에 유리합니다. 그러나 API 계약이 변경될 때는 공용 data class를 통해 즉시 영향을 파악할 수 있어, 두 모델을 병행하는 전략이 안정적입니다.

CI/CD 파이프라인에는 JSON 스냅샷을 이용한 테스트를 추가해, 서버 응답 변경 시 빌드가 실패하도록 설정하세요. JSON2Class로 생성한 Markdown 표를 QA나 기획과 공유하면, 필수/선택 필드를 팀 차원에서 합의하기 좋습니다.

팀 핸드오프 체크리스트

  1. 샘플 JSON과 생성된 data class를 PR 본문에 함께 첨부한다.
  2. 직렬화 라이브러리와 nullable 정책을 README에 명시한다.
  3. 테스트 픽스처를 `resources/` 폴더에 JSON으로 저장하고, 스냅샷 테스트나 golden test에 활용한다.
  4. 프런트엔드가 TypeScript를 쓴다면, 동일 JSON을 TypeScript 탭에도 붙여 인터페이스를 맞춰본다.

이 과정을 거치면 신규 팀원이 합류하더라도 API 계약을 빠르게 이해하고, 플랫폼마다 다른 직렬화 스택을 사용하는 경우에도 일관성을 유지할 수 있습니다.

무엇보다 변환기는 브라우저에서만 동작해 입력 JSON이 서버에 저장되지 않습니다. 안심하고 샘플을 붙여 넣고, 필요하다면 가명화 버전으로 먼저 실험한 뒤 실제 코드에 반영하세요. 필요하면 언어별 가이드와 TypeScript/Java 페이지를 함께 참조해 팀 전체의 모델을 맞출 수 있습니다.

JSON 변환 바로가기