역직렬화는 JSON, XML 또는 Protobuf 데이터 스트림에서 객체를 복원하는 프로세스로, 원격 API와 작동하는 모든 모바일 애플리케이션에 필수적입니다. Apple Developer (2026)에 따르면, 수신 데이터의 잘못된 처리는 기기에서 충돌의 일반적인 원인 중 하나로 남아 있습니다. iOS의 JSONDecoder와 Android의 Gson은 표준 도구이지만, 각각 고유한 특징과 한계가 있습니다.
핵심 요약
역직렬화는 바이트 스트림 또는 구조화된 텍스트를 프로그래밍 언어 객체로 변환하는 프로세스입니다. 모바일 개발에서 이 프로세스는 애플리케이션이 서버로부터 응답을 받을 때마다 발생합니다. JSON 문자열이 User, Order 또는 Product 클래스의 인스턴스로 변환됩니다. 사용자에게 데이터를 표시하는 화면의 안정성은 역직렬화의 정확성에 직접적으로 의존합니다.
직렬화와 역직렬화는 상호 역 프로세스이며, 실제로는 거의 대칭적이지 않습니다. 직렬화는 서버로 보내기 위해 객체를 문자열로 변환하는 반면, 역직렬화는 수신된 문자열에서 객체를 복원합니다. 서버는 클라이언트 모델에 없는 필드를 보내거나, 다른 날짜 형식을 사용하거나, 숫자 대신 null을 반환할 수 있습니다. Square Engineering (2025)에 따르면, 형식 비대칭성은 Android 애플리케이션에서 네트워크 계층 오류의 23%를 유발합니다. 위험을 줄이기 위해 OpenAPI를 통한 스키마 버전 관리와 엄격한 계약 명세가 사용됩니다.
JSON은 사람이 읽기 쉽고 내장 지원 덕분에 모바일 API에서 가장 인기 있는 형식으로 남아 있습니다. Google의 Protobuf는 고부하 시스템에서 사용되며 JSON보다 3~6배 더 컴팩트하고 구문 분석이 빠르지만, .proto 파일에서 코드 생성이 필요하고 도구 없이는 읽을 수 없습니다. XML은 최신 모바일 애플리케이션에서 덜 일반적이지만, 엔터프라이즈 시스템의 SOAP 서비스와 Android 구성 파일에서 사용됩니다. MessagePack은 구조가 JSON과 유사하지만 더 컴팩트한 바이너리 형식으로, 실시간 시스템에서 인기가 있습니다.
역직렬화 프로세스는 세 단계를 거칩니다. 첫째, 토큰화가 원시 텍스트를 키, 문자열, 숫자, 구분 기호와 같은 토큰으로 분할합니다. 그런 다음 구문 분석이 구조의 정확성(괄호가 닫혀 있는지, 따옴표 유형이 올바른지, 형식이 RFC 8259를 준수하는지)을 확인합니다. 마지막 단계는 애플리케이션의 객체 모델에 대한 매핑으로, 각 JSON 키에 명명 전략을 고려하여 클래스 속성이 할당됩니다.
모바일 개발에서는 매핑에 두 가지 접근 방식이 있습니다. Reflection(Gson, JSONSerialization)은 Java Reflection API 또는 Objective-C 런타임을 통해 런타임에 클래스 구조를 분석합니다. 유연하고 추가 구성이 필요하지 않지만 느리고 더 많은 메모리를 소비합니다. Code generation(Moshi codegen, kotlinx.serialization, Codable)은 컴파일 타임에 코드를 생성하여 더 빠르고 타입 안전하며 reflection을 통해 내부 구조를 노출하지 않습니다. JetBrains와 Square는 프로덕션 빌드에 코드 생성을 권장하며, Google 벤치마크에서 성능이 2~4배 향상됩니다.
struct User: Codable {
let id: Int
let name: String
let email: String
let createdAt: Date
}
let json = """
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"created_at": "2026-06-01T12:00:00Z"
}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let user = try decoder.decode(User.self, from: data)
Swift에서 JSON을 User 모델로 역직렬화하는 예제입니다. convertFromSnakeCase 전략은 snake_case API 키를 camelCase 모델 속성으로 자동 변환합니다. 이는 iOS 프로젝트의 표준 관행입니다. data 매개변수는 URLSession을 통해 수신된 서버 응답의 원시 바이트입니다. try를 통한 오류 처리를 통해 애플리케이션이 충돌하지 않고 잘못된 JSON을 포착할 수 있습니다.
JSONDecoder는 네 가지 키 전략을 지원합니다: useDefaultKeys(정확한 일치), convertFromSnakeCase(snake_case → camelCase), custom(클로저), convertFromKebabCase(kebab-case → camelCase). 날짜의 경우 .iso8601, .secondsSince1970, .millisecondsSince1970 및 사용자 정의 dateFormatter를 사용할 수 있습니다. 올바른 전략을 선택하는 것은 강력한 역직렬화를 위한 첫 번째 단계이며, 대부분의 형식 불일치 오류를 방지합니다.
JSONDecoder는 iOS SDK의 표준 역직렬화 메커니즘으로, Codable 프로토콜과 함께 작동합니다. JSONDecoder는 자동으로 JSON을 struct 또는 class 인스턴스로 구문 분석하며, 중첩 객체, 배열 및 기본 유형을 지원합니다. 사용자 정의 로직에는 init(from: Decoder) 메서드가 사용됩니다. 이를 통해 비표준 형식 처리, 이전 API 버전에서 누락된 필드 처리, 또는 여러 JSON 키를 하나의 속성으로 결합할 수 있습니다.
struct Order: Decodable {
let orderId: String
let amount: Double
let status: OrderStatus
enum OrderStatus: String, Decodable {
case pending, confirmed, shipped, cancelled
}
}
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let order = try decoder.decode(Order.self, from: jsonData)
DateDecodingStrategy는 JSONDecoder가 날짜 문자열을 해석하는 방식을 결정합니다. .iso8601이 가장 일반적으로 사용되며 표준 REST API 형식입니다. 중첩된 enum OrderStatus는 JSON 문자열 값에서 자동으로 디코딩됩니다. 이는 매직 넘버를 피하고 코드를 자체 문서화하게 만듭니다. 주문 상태는 항상 엄격하게 정의된 값 집합을 갖습니다.
Swift 4.2부터 Codable은 개별 속성의 사용자 정의 역직렬화를 위해 property wrappers를 지원합니다. @DefaultValue는 JSON에 필드가 없을 때 기본값을 설정하는 인기 있는 래퍼입니다. @LosslessString은 문자열을 숫자로 또는 그 반대로 변환합니다. 이는 서버가 id를 문자열 "123"으로 보내지만 모델이 Int를 기대할 때 특히 유용합니다. Property wrappers는 init(from:)의 상용구 코드를 줄이고 모델을 더 깔끔하게 만듭니다.
Android에서 역직렬화 라이브러리의 선택은 언어와 프로젝트 요구 사항에 따라 달라집니다. Google의 Gson은 가장 일반적인 옵션으로, reflection을 통해 작동하지만 복잡한 계층 구조에서 성능 문제가 있습니다. Square의 Moshi는 reflection과 code generation을 모두 지원하며, 메모리 소비가 적고 큰 응답을 더 빠르게 처리합니다. JetBrains의 kotlinx.serialization은 컴파일러 통합을 갖춘 네이티브 Kotlin 솔루션으로, reflection을 전혀 사용하지 않습니다.
@Serializable
data class User(
@SerialName("user_id")
val userId: Int,
val name: String,
val email: String,
@SerialName("created_at")
val createdAt: String
)
val json = Json { ignoreUnknownKeys = true }
val user = json.decodeFromString<User>(response)
@Serializable은 클래스에 대한 코드 생성을 활성화하는 Kotlin 컴파일러 어노테이션입니다. ignoreUnknownKeys 매개변수는 서버가 모델에 없는 필드를 보낼 때 충돌을 방지합니다. snake_case 키 매핑을 위해 @SerialName이 사용되며, 이는 iOS의 convertFromSnakeCase와 동일합니다. JetBrains (2026)에 따르면, 이 라이브러리는 멀티플랫폼을 지원합니다. 동일한 Serializable 클래스가 Android, iOS(KMP) 및 서버 측 Kotlin에서 작동합니다.
라이브러리 선택은 속도와 유연성 간의 균형에 달려 있습니다. Gson은 프로토타입 및 Java 프로젝트에 적합하며 어노테이션이 필요 없고 즉시 사용할 수 있습니다. Moshi는 중간 위치를 차지합니다. @JsonClass(generateAdapter = true)를 통한 codegen은 kotlinx.serialization에 가까운 속도를 제공하고, reflection 모드는 Gson의 유연성을 제공합니다. kotlinx.serialization은 순수 Kotlin 프로젝트에 가장 빠른 옵션이지만 Kotlin 1.4+와 Gradle의 Kotlin Serialization 플러그인이 필요합니다.
| 라이브러리 | 메커니즘 | 속도 | KMP |
|---|---|---|---|
| Gson | Reflection | 낮음 | 아니요 |
| Moshi | Reflection / Codegen | 중간 / 높음 | 아니요 |
| kotlinx.serialization | 컴파일러 codegen | 높음 | 예 |
Type mismatch는 JSON에 한 유형의 값이 포함되어 있지만 모델이 다른 유형을 기대하는 상황입니다. 서버가 숫자 대신 문자열 "42"를 보내거나 부울 true 대신 숫자 1을 보냅니다. iOS에서 JSONDecoder는 기본적으로 DecodingError.typeMismatch를 throw합니다. Android에서 Gson은 변환을 시도하지만 Moshi와 kotlinx.serialization은 명시적인 어댑터가 필요합니다. 해결책은 특정 필드에 lenient 전략 또는 사용자 정의 역직렬화 프로그램을 사용하는 것입니다.
서버가 선택적 필드를 포함하지 않으면 코드가 오류와 함께 충돌합니다. Swift의 Optional 필드와 Kotlin의 nullable 유형이 이 문제를 해결합니다. 필드가 null이거나 JSON에 없으면 속성이 nil/null을 받고 애플리케이션이 계속 작동합니다. 필수 필드의 경우 역직렬화 전에 API 클라이언트 수준에서 필드 존재 여부를 확인하는 것이 좋습니다. Moshi와 kotlinx.serialization은 기본적으로 모든 필드가 필요합니다. nullable 표시와 기본값이 이 제한을 제거합니다.
서버의 JSON 구조 변경은 프로덕션 충돌의 일반적인 원인입니다. 표준 관행은 루트 객체의 version 필드를 통한 스키마 버전 관리와 클라이언트에서 2~3개의 이전 버전을 지원하는 것입니다. kotlinx.serialization은 다른 버전에 대해 여러 모델을 선언하고 JsonElement로 초기 구문 분석 후 version 필드를 기반으로 올바른 모델을 선택할 수 있습니다. 추가 보호에는 새 필드에 대한 ignoreUnknownKeys와 제거될 수 있는 필드에 대한 기본값이 포함됩니다.
| 오류 | 증상 | 보호 기능이 있는 라이브러리 |
|---|---|---|
| Type mismatch | DecodingError / 예외 | kotlinx — coerceInputValues = true |
| 누락된 필드 | 액세스 시 충돌 | Moshi — @Transient + default |
| 잘못된 날짜 형식 | 디코딩 오류 | JSONDecoder — dateDecodingStrategy |
| 추가 필드 | 무시되거나 충돌 | kotlinx — ignoreUnknownKeys = true |
| 널이 아닌 필드의 Null | 런타임 충돌 | Moshi — lenient + @Nullable |
역직렬화 오류 로깅은 프로덕션에서 필수적인 관행입니다. decode를 do/catch로 감싸고 원시 JSON과 예상 모델 유형을 Crashlytics 또는 Sentry에 기록하십시오. 이를 통해 어떤 API의 어떤 필드가 어떤 앱 버전에서 손상되었는지 신속하게 식별할 수 있습니다. 로깅이 없으면 역직렬화 오류는 컨텍스트 없이 신비로운 충돌처럼 보입니다.
자주 묻는 질문
파싱은 타입화된 모델을 반드시 생성하지 않고 구조화된 텍스트를 구성 요소로 분석하는 것입니다. 역직렬화는 알려진 속성 유형을 가진 완전한 언어 객체를 결과로 생성하는 파싱의 특정 사례입니다. 파싱은 스트리밍 방식이 가능하지만, 역직렬화는 항상 완전한 객체를 생성합니다.
순수 Kotlin 프로젝트의 경우 kotlinx.serialization을 권장합니다. 컴파일러에 통합되어 있고 reflection을 사용하지 않으며 Kotlin Multiplatform을 지원합니다. 기존 Java 프로젝트의 경우 code generation과 함께 Moshi를 권장합니다. Gson은 교체에 상당한 노력이 필요한 레거시 프로젝트에 남겨두는 것이 좋습니다.
iOS에서는 JSONDecoder에서 keyDecodingStrategy = .convertFromSnakeCase를 사용합니다. Android의 kotlinx.serialization에서는 각 필드에 @SerialName을 사용합니다. Moshi에서는 @Json(name="field_name") 또는 전역 JsonAdapter.Factory를 적용합니다. 프로젝트 수준의 일관된 스타일은 API 계약에서 합의된 모범 사례입니다.
가장 흔한 이유는 필수로 선언된 필드에서 서버의 예기치 않은 null입니다. 개발 환경에서는 서버가 완전한 데이터를 반환하지만, 프로덕션에서는 축약된 응답을 반환합니다. 해결책: 누락될 수 있는 모든 필드를 Kotlin에서는 nullable, Swift에서는 optional로 표시하고 ignoreUnknownKeys와 기본값을 사용합니다.
Code generation(Moshi codegen, kotlinx.serialization, Codable)은 Google 벤치마크에서 reflection보다 2~4배 빠릅니다. 속도 외에도 코드 생성은 더 타입 안전하며 런타임에 클래스 메타데이터가 필요하지 않고 타입 오류가 역직렬화 중이 아닌 컴파일 타임에 포착됩니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.