๐—ถ๐—ข๐—ฆ/๐Ÿ iOS ๊ฐœ๋…ํŽธ

[iOS / Swift] Codable & CodingKeys

z_ero 2025. 11. 11. 16:43

๐ŸŽ 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๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ง์ ‘ ๋งคํ•‘ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜์‘ํ˜•