Codable 프로토콜은 CodingKeys라는 이름의 enum을 자동으로 찾아 사용한다. CodingKeys라는 이름을 사용하면 별도의 추가 코드 없이 자동으로 인코딩/디코딩이 가능해지게 설계되었다.
https://github.com/swiftlang/swift/blob/main/lib/Sema/DerivedConformanceCodable.cpp
/// 열거형(case)의 CodingKey에 대한 식별자를 계산합니다.
static Identifier caseCodingKeysIdentifier(const ASTContext &C,
EnumElementDecl *elt) {
llvm::SmallString<16> scratch;
camel_case::appendSentenceCase(scratch, elt->getBaseIdentifier().str());
scratch += C.Id_CodingKeys.str();
return C.getIdentifier(scratch.str());
}
/// \c target에 중첩된 \c CodingKeys 열거형을 가져옵니다.
/// "CodingKeys" 엔티티가 타입 별칭(typealias)일 경우, 타입 별칭을 통해 접근할 수 있습니다.
///
/// 이는 오직 \c CodingKeys 열거형이 유효한지 확인되었을 때(즉, \c hasValidCodingKeysEnum를 통해)
/// 또는 생성되었을 때(즉, \c synthesizeCodingKeysEnum를 통해)만 유용합니다.
///
/// \param C 조회를 수행할 \c ASTContext입니다.
///
/// \param target 조회할 대상 타입입니다.
///
/// \return \c target이 유효한 \c CodingKeys 열거형을 가지고 있다면 이를 반환합니다;
/// 그렇지 않다면 \c nullptr을 반환합니다.
위의 장점을 무시하고 CodingKeys라는 이름을 쓰지 않아야할 특별한 상황이 있을 경우, 코드가 많이 늘어나지만 가능은 하다.
import Foundation
struct CurrentWeatherResult: Codable {
let weather: [Weather]
let main: WeatherMain
enum AppleKeys: String, CodingKey {
case weather
case main
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AppleKeys.self)
weather = try container.decode([Weather].self, forKey: .weather)
main = try container.decode(WeatherMain.self, forKey: .main)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AppleKeys.self)
try container.encode(weather, forKey: .weather)
try container.encode(main, forKey: .main)
}
}
// MARK: -----
struct Weather: Codable {
let id: Int
let main: String
let description: String
let icon: String
enum AppleKeys: String, CodingKey {
case id
case main
case description
case icon
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AppleKeys.self)
id = try container.decode(Int.self, forKey: .id)
main = try container.decode(String.self, forKey: .main)
description = try container.decode(String.self, forKey: .description)
icon = try container.decode(String.self, forKey: .icon)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AppleKeys.self)
try container.encode(id, forKey: .id)
try container.encode(main, forKey: .main)
try container.encode(description, forKey: .description)
try container.encode(icon, forKey: .icon)
}
}
// MARK: -----
struct WeatherMain: Codable {
let temp: Double
let tempMin: Double
let tempMax: Double
let humidity: Int
enum AppleKeys: String, CodingKey {
case temp
case tempMin = "temp_min"
case tempMax = "temp_max"
case humidity
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: AppleKeys.self)
temp = try container.decode(Double.self, forKey: .temp)
tempMin = try container.decode(Double.self, forKey: .tempMin)
tempMax = try container.decode(Double.self, forKey: .tempMax)
humidity = try container.decode(Int.self, forKey: .humidity)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: AppleKeys.self)
try container.encode(temp, forKey: .temp)
try container.encode(tempMin, forKey: .tempMin)
try container.encode(tempMax, forKey: .tempMax)
try container.encode(humidity, forKey: .humidity)
}
}
// JSON 디코딩
let jsonData = """
{
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"main": {
"temp": 293.55,
"temp_min": 289.82,
"temp_max": 295.37,
"humidity": 36
}
}
""".data(using: .utf8)!
do {
let weatherResult = try JSONDecoder().decode(CurrentWeatherResult.self, from: jsonData)
print(weatherResult)
} catch {
print("Decoding error: \(error)")
}