로우코드 모바일 플랫폼을 개발하다 보면, 서버에서 객체의 타입이 불분명하게(Any
) 내려오는 경우가 자주 발생합니다. 이를 위해 유동적인 타입에 대처하기 위한 방법이 필요했습니다. Codable
을 사용해 JSON을 Swift 객체로 변환하는 데에도 한계가 생기기 때문에, 이러한 상황을 안정적으로 처리하기 위한 방법을 작성한 글입니다.
문제 상황
예를 들어 서버에서 다음과 같은 JSON 데이터를 반환했습니다.
{
"id": 1,
"name": "John Doe",
"age": "30",
"description": "This is a sample description."
}
위와 같이 age
필드가 String
으로 내려올 때도 있지만, 다음처럼 Int
타입으로 내려올 때도 있습니다.
{
"id": 1,
"name": "John Doe",
"age": 30,
"description": "This is a sample description."
}
이러한 상황에서 age
필드를 기본적으로 Int
타입으로 사용하면서도, String
타입으로 내려오는 경우도 유연하게 대응할 수 있어야합니다.
Step 1: 모델 정의
먼저 age
필드를 Int
타입으로 정의하고, 서버에서 Int
또는 String
형태로 값이 내려오더라도 모두 Int
로 변환하여 받아들일 수 있도록 커스텀 디코딩 로직을 작성합니다.
struct Person: Decodable {
let id: Int
let name: String
let age: Int?
let description: String
enum CodingKeys: String, CodingKey {
case id, name, age, description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// 기본 필드를 디코딩
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
description = try container.decode(String.self, forKey: .description)
// age 필드를 Int 또는 String으로 받아들이고, null 값도 처리
if let ageInt = try? container.decode(Int.self, forKey: .age) {
age = ageInt // Int 타입으로 성공 시 그대로 할당
} else if let ageString = try? container.decode(String.self, forKey: .age),
let ageFromString = Int(ageString) {
age = ageFromString // String 타입으로 내려올 경우 Int로 변환하여 할당
} else {
age = nil // age가 null이거나 변환할 수 없는 경우 nil로 설정
}
}
}
코드 설명
age
필드를Int?
타입으로 정의하여, 값이 없거나 변환할 수 없는 경우nil
로 저장되도록 합니다.age
가Int
로 디코딩될 수 있으면ageInt
로 할당합니다.age
가String
으로 디코딩될 경우,Int
로 변환 가능할 때만 변환한 값을 할당합니다.- 만약
age
가null
이거나 변환할 수 없는 타입이라면age
는nil
로 저장됩니다.
Step 2: 네트워크 요청 함수
다음은 네트워크 요청을 통해 데이터를 받아오고 Person
객체로 변환하는 함수입니다.
import Foundation
func fetchPersonData(completion: @escaping (Result<Person, Error>) -> Void) {
guard let url = URL(string: "https://api.example.com/person") else {
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
let error = NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "No data returned"])
completion(.failure(error))
return
}
do {
let decoder = JSONDecoder()
let person = try decoder.decode(Person.self, from: data)
completion(.success(person))
} catch let decodingError {
completion(.failure(decodingError))
}
}.resume()
}
Step 3: 결과 확인
이제 fetchPersonData
함수를 호출할 때, age
필드가 Int
, String
, 또는 null
로 내려와도 문제없이 처리됩니다.
fetchPersonData { result in
DispatchQueue.main.async {
switch result {
case .success(let person):
if let age = person.age {
print("Person received with age: \(age)")
} else {
print("Person received with no age information")
}
case .failure(let error):
print("Failed to fetch person data: \(error)")
}
}
}
이 방법을 사용하면 서버의 데이터 구조가 변경될 때 발생할 수 있는 디코딩 에러를 줄일 수 있습니다.
'iOS' 카테고리의 다른 글
Fastlane에서 Unauthorized Access 에러 해결: 세션 갱신 방법 (0) | 2024.11.25 |
---|---|
Swift에서 가독성 높은 Logger 클래스 만들기 (0) | 2024.11.10 |