[iOS / Swift] Codable & CodingKeys
๐ Codable์ด๋?
Swift์์ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋, ์๋ฒ์ JSON ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ฆฌ ์ฑ์ ๊ตฌ์กฐ์ฒด๋ ํด๋์ค ํํ๋ก ๋ฐ๊ฟ์ผ ํ ๋๊ฐ ๋ง์ต๋๋ค.
์ด๋ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ฐ๋ก Codable์
๋๋ค!
๊ณต์ ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด Codable์ ์์ ์ ์ธ๋ถ ํํ์ผ๋ก ๋ณํํ๊ฑฐ๋(Encode),
์ธ๋ถ ํํ์ผ๋ก๋ถํฐ ์์ ์ผ๋ก ๋ณํํ ์ ์๋(Decode) ํ์ ์ด์์.
์ฆ, Swift์ ๊ฐ์ฒด๋ฅผ JSON์ผ๋ก ๋ฐ๊พธ๊ฑฐ๋, JSON์ Swift ๊ฐ์ฒด๋ก ๋ฐ๊พธ๋ ์ญํ ์ ํ๋ ํ๋กํ ์ฝ์ธ๊ฑฐ์ฃ .
Codable์ ์ฌ์ค ๋ ๊ฐ์ ํ๋กํ ์ฝ์ด ํฉ์ณ์ง ํํ์ธ๋ฐ์ !
| ํ๋กํ ์ฝ | ์ญํ |
| Encodable | ์์ ์ JSON ๊ฐ์ ์ธ๋ถ ๋ฐ์ดํฐ๋ก ์ธ์ฝ๋ฉํ ์ ์์ต๋๋ค. |
| Decodable | ์ธ๋ถ ๋ฐ์ดํฐ๋ฅผ ์์ ์ผ๋ก ๋์ฝ๋ฉํ ์ ์์ต๋๋ค. |
| Codable | Encodable + Decodable ๋ ๋ค ๊ฐ๋ฅ |
Codable์ ์ฑํํ๋ค๋ ๊ฒ์ ๊ทธ ํ์ ์ด ์ธ์ฝ๋ฉ๋ ๋๊ณ , ๋์ฝ๋ฉ๋ ๋๋ ํ์ ์ด๋ผ๋ ๋ป์ ๋๋ค.
๋ํ Codable์ ํ๋กํ ์ฝ์ด๊ธฐ ๋๋ฌธ์ struct, class, enum ๋ฑ ๋ชจ๋ ํ์ ์์ ์ฑํํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ฑ์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค๊ณ ๊ฐ์ ํด๋ด ์๋ค.
struct User: Codable {
let name: String
let age: Int
}
์ด ๊ตฌ์กฐ์ฒด๋ ์ด์ JSON์ผ๋ก ๋ณํํ ์๋ ์๊ณ , JSON ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ๋ค์ User ํ์ ์ผ๋ก ๋ฐ๊ฟ ์๋ ์๋๊ฑฐ์ฃ .
Encoding (Swift → JSON)
์๋ฒ๋ก ๋ณด๋ผ ๋ฐ์ดํฐ๋ฅผ JSON์ผ๋ก ๋ฐ๊พธ๋ ค๋ฉด ์๋์ ๊ฐ์ JSONEncoder๋ฅผ ์ฌ์ฉํฉ๋๋ค.
struct User: Codable {
let name: String
let age: Int
}
let me = User(name: "์ ๋ก", age: 23)
let encoder = JSONEncoder()
if let data = try? encoder.encode(me),
let jsonString = String(data: data, encoding: .utf8) {
print(jsonString)
}
์ถ๋ ฅ ๊ฒฐ๊ณผ๋ ์ด๋ ๊ฒ ๋์ค๊ฒ ์ฃ ?
{"name":"์ ๋ก","age":23}
์ด๊ฒ ๋ฐ๋ก Swift ๊ตฌ์กฐ์ฒด๊ฐ JSON ํํ๋ก ๋ฐ๋ ๋ชจ์ต์ ๋๋ค.
ํ: JSONEncoder๋ ๊ธฐ๋ณธ์ ์ผ๋ก ํ ์ค๋ก ์ถ๋ ฅ๋๊ธฐ ๋๋ฌธ์, outputFormatting ์ต์ ์ ์ค์ ํ๋ฉด ๋ณด๊ธฐ ์ข๊ฒ ์ค๋ฐ๊ฟ์ด ๋ค์ด๊ฐ JSON์ ์ถ๋ ฅํ ์ ์์ด์!
Decoding (JSON → Swift)
์ด๋ฒ์๋ ์๋ฒ์์ JSON ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ User๋ก ๋ณํํ๋ ๊ฒฝ์ฐ๋ฅผ ๋ด ์๋ค.
struct User: Codable {
let name: String
let age: Int
}
let jsonData = """
{"name": "์ ๋ก", "age": 23}
""".data(using: .utf8)!
let decodedUser = try? JSONDecoder().decode(User.self, from: jsonData)
print(decodedUser?.name ?? "")
decode(_:from:) ๋ฉ์๋์ ์ ๋ค๋ฆญ ํ์ (User.self)์ ๋๊ฒจ์ฃผ๋ฉด, JSON ๋ฐ์ดํฐ๋ฅผ ์์์ ํด๋น ํ์ ์ผ๋ก ๋ฐ๊ฟ์ค๋๋ค.
์ด ๊ณผ์ ์ด ๋ฐ๋ก ๋์ฝ๋ฉ์ด์์!
Optional ์์ฑ๊ณผ ๊ธฐ๋ณธ๊ฐ
์ค์ API๋ฅผ ๋ค๋ฃจ๋ค ๋ณด๋ฉด ๊ฐ์ด null์ด๊ฑฐ๋ ์์ ํค๊ฐ ๋น ์ง JSON์ด ์ข ์ข ์์ต๋๋ค.
์ด๋ด ๋๋ ์ด๋ป๊ฒ ํด์ค์ผ ํ ๊น์?
ํด๋น ์์ฑ์ Optional๋ก ์ ์ธํด์ฃผ๋ฉด ๋ฉ๋๋ค!
struct User: Codable {
let name: String
let age: Int?
}
๋ง์ฝ ๊ฐ์ด ์์ ๋ ๊ธฐ๋ณธ๊ฐ์ ์ฃผ๊ณ ์ถ๋ค๋ฉด decodeIfPresent๋ฅผ ํ์ฉํ ์๋ ์์ด์.
struct User: Codable {
let name: String
let age: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
age = try container.decodeIfPresent(Int.self, forKey: .age) ?? 0
}
}
์ด๋ ๊ฒ ํ๋ฉด age๊ฐ ์๊ฑฐ๋ null์ด์ด๋ ์๋์ผ๋ก 0์ด ๋ค์ด๊ฐ๊ฒ ์ฃ ?
CodingKeys
์๋ฒ์์ ์ค๋ JSON์ด snake_case๋ก ๋์ด ์๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ด๋ฐ JSON์ด ์๋ค๊ณ ํด๋ณผ๊น์?
{
"user_name": "์ ๋ก",
"profile_image_url": "<https://example.com/me.png>"
}
Swift์์๋ ๋ณดํต camelCase๋ฅผ ์ฐ๊ธฐ ๋๋ฌธ์ ์ด ์ด๋ฆ๋ค์ ์ง์ ์ฐ๊ฒฐํด์ค์ผ ํฉ๋๋ค.
ํ๋ํ๋ ์ด๋ฆ์ ๋ฐ๊ฟ์ฃผ๋ ค๋ฉด ์ค๋ฅ๋ ๋ง์ด ์๊ธฐ๊ณ ๋ฒ๊ฑฐ๋กญ๊ฒ ์ฃ ?
์ด๋ ์ฌ์ฉํ๋ ๊ฒ ๋ฐ๋ก CodingKeys์ ๋๋ค.
struct User: Codable {
let userName: String
let profileImageURL: String
enum CodingKeys: String, CodingKey {
case userName = "user_name"
case profileImageURL = "profile_image_url"
}
}
์ด๋ ๊ฒ ํด๋๋ฉด ์๋ฒ์์ ๋ฐ์ user_name์ด userName์ผ๋ก, profile_image_url์ด profileImageURL๋ก ์๋ ๋งคํ๋ฉ๋๋ค.
์๋ ๋ณํ ์ต์
CodingKeys๋ฅผ ํ๋ํ๋ ์ ๊ธฐ ๊ท์ฐฎ์ ๋๊ฐ ์์ฃ ? ๊ทธ๋ฐ ๋ถ๋ค์ ์ํ ์๋ ๋ณํ ์ต์ ์ ๋๋ค.
๋ฐ๋ก keyDecodingStrategy์ธ๋ฐ์!
ํด๋น ์์ฑ์ ์ด์ฉํด์ ์๋์ผ๋ก snake_case → camelCase ๋ณํ์ ์ ์ฉํ ์ ์์ต๋๋ค.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
์ด ์ต์ ์ ์ค์ ํด๋๋ฉด JSON์ user_name์ด ์๋์ผ๋ก Swift์ userName์ผ๋ก ๋ฐ๋๋๋ค.
๋๋ถ์ CodingKeys๋ฅผ ๋ฐ๋ก ์์ฑํ์ง ์์๋ ๋ผ์ ์ฝ๋๊ฐ ํจ์ฌ ๊น๋ํด์ ธ์.
๋ค๋ง, ํ ๊ฐ์ง ์ฃผ์ํ ์ ์ด ์์ต๋๋ค!
keyDecodingStrategy๋ ๋จ์ํ ์ด๋ฆ ๊ท์น๋ง ๋ณํํด์ฃผ๋ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์, desc → description์ฒ๋ผ ์ด๋ฆ ์์ฒด๊ฐ ์ ํ ๋ค๋ฅธ ๊ฒฝ์ฐ์๋ ์ฌ์ ํ CodingKeys๋ฅผ ์ฌ์ฉํด์ ์ง์ ๋งคํํด์ค์ผ ํฉ๋๋ค.