[TIL] Codable

Codable이란?

외부 표현과의 호환성을 위해 데이터 유형을 인코딩/디코딩할 수 있는 프로토콜.

JSON 데이터 등을 인코딩/디코딩하기 위해 사용됨

 

Decodable

데이터를 원하는 모델의 형식으로 디코딩할 수 있도록 만든 프로토콜

 

// json data
let json = """
{
"quote": "Count your age by friends, not years. Count your life by smiles, not tears.",
"author": "John Lennon"
}
"""

// MARK: Quote
struct Quote: Decodable {
    let quote: String
    let author: String
}

// MARK: String -> Data 형식으로 바꿔주기 -> Quote에 담아주기
// 1. String -> Data
guard let result = json.data(using: .utf8) else { fatalError("ERROR")}
print(result)

// 2. Data -> Quote
do {
    let value = try JSONDecoder().decode(Quote.self, from: result)
    print(value)
} catch {
    print(error)
}

예제) JSON 데이터를 가져왔고 Quote라는 구조체를 만들었다.

JSON 데이터를 Quote의 형식으로 저장하기 위해서는 String인 JSON을 Data 형식으로 바꿔준 뒤, 구조체에 담아주어야 한다.

1. String -> Data 로 변환하기 위한 코드와

2. Data -> Quote 로 변환하기 위한 코드를 작성하였다.

 

 

DecodingStrategy

주의해야 할 점은

키가 완벽하게 일치하지 않으면 실행이 아예 안 될 수도 있다는 것.

  • 1. 가장 쉽게 고치는 방법은?
    • 옵셔널로 정의한다. String? ⇒ 그러나 모든 해결책은 아님
  • 2. snake_case에서 camelCase로 변환?
    • snake_case에서 변환하는 Strategy를 추가해주면 변경된다
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
  • 3. 아예 키 이름을 다르게 정의하고 싶다면?
    • CodingKey 프로토콜 사용
struct Quote: Decodable { 
    let quote: String 
    let author: String 
    
    enum CodingKeys: String, CodingKey { // 내부적으로 선언되어 있는 열거형 
    	case quote = "quote_content" 
        case author = "author_name" 
    } 
}

 

  • 서버에서 값을 3개 줬는데 4개로 만들고 싶을 경우?
    • init으로 선언해준다
let json = """
{
"quote_content": "Count your age by friends, not years. Count your life by smiles, not tears.",
"author_name": null,
"like_count": 12345
}
"""

struct Quote: Decodable {
    let quote: String
    let author: String
    let like: Int
    let isInfluencer: Bool // 10000개 이상 좋아요 받은 경우
    
    enum CodingKeys: String, CodingKey { // 내부적으로 선언되어 있는 열거형
        case quote = "quote_content"
        case author = "author_name"
        case like = "like_count"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        quote = try container.decode(String.self, forKey: .quote)
        author = try container.decodeIfPresent(String.self, forKey: .author) ?? "unknown" // null이라면 unknown으로
        like = try container.decode(Int.self, forKey: .like)
        isInfluencer = (10000...).contains(like) ? true : false
    }
}

 

 

Encodable

모델 데이터를 외부로 전달하기 위해 JSON 데이터 등으로 변환할 때 사용하는 프로토콜

// User
struct User: Encodable {
    let name: String
    let signDate: Date
    let age: Int
}

// users
let users: [User] = [
    User(name: "Ziuge", signDate: Date(), age: 33),
    User(name: "Elsa", signDate: Date(timeInterval: -86400, since: Date()), age: 18),
    User(name: "Emily", signDate: Date(timeIntervalSinceNow: 86400*2), age: 11)
]

do {
    // 1. User -> Data
    let result = try JSONEncoder().encode(users) 
    print(result)
    
    // 2. Data -> String
    guard let jsonString = String(data: result, encoding: .utf8) else { 
        fatalError("ERROR")
    }
    print(jsonString)
} catch {
    print(error)
}

모델 -> Data -> String 의 순서로 변경한다

(Decodable과 반대)

 

EncodableStrategy

  • prettyPrinted : JSON 데이터를 깔끔하게 보고 싶을 때
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
  • dateEncodingStrategy : 날짜를 깔끔하게 보여줌
encoder.dateEncodingStrategy = .iso8601