Reference


🐯 Encoding/Decoding Custom Types

✔️ 개요

많은 프로그래밍 task들은 data를 네트워크 연결을 통해 전달하거나 disk에 저장하고 API나 서비스에 제출하는 것들을 포함합니다
이런 task들은 대게 data를 전송할 때 중간 format으로 인코딩/디코딩해야 합니다

Swift 표준 라이브러리는 data 인코딩/디코딩을 위한 표준화된 접근법을 정의합니다
당신의 custom type에 Encodable/Decodable 프로토콜을 채택하여 적용할 수 있습니다

이 프로토콜들을 채택하는 것은 Encoder/Decoder 프로포콜이 data를 취하여 JSON이나 프로퍼티 리스트(.plist)같은 외부표현으로 인코딩/디코딩하도록 합니다
(참고로, Codable == Encodable & Decodable)

✔️ Codable 프로토콜 자동 준수

Codable한 타입의 프로퍼티만으로 구성된 custom type은 Codable 프로포톨을 선언만 하여도 자동으로 준수됩니다

따라서, custom type을 인코딩/디코딩 가능하도록 만들기 위한 가장 간단한 방법은 이미 Codable한 타입의 프로퍼티만 선언하는 것입니다
(ex. String/Int/Double 같은 표준 타입과 Date/Data/URL 등)

struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
}

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    
    // Landmark is still codable after adding these properties.
    var vantagePoints: [Coordinate]
    var metadata: [String: String]
    var website: URL?
}

✔️ Encodable / Decodable 중 하나만 적용할 수도 있다

인코딩/디코딩 중 한 방향만 사용한다면 굳이 Codable로 모두 채택하지 않아도 된다

struct Landmark: Encodable {
    var name: String
    var foundingYear: Int
}
struct Landmark: Decodable {
    var name: String
    var foundingYear: Int
}

✔️ CodingKeys로 프로퍼티를 매칭

Codable 프로토콜을 채택한 타입은 CodingKeys라는 특별한 열거형을 nested type으로 선언할 수 있습니다 (Codingkeys 열거형은 CodingKey 프로토콜을 채택합니다)

이 열거형이 존재하면, 각 case들은 인코딩/디코딩할 때 포함되어야 할 프로퍼티들의 리스트 역할을 합니다

🔘 case의 이름은 당신의 custom type의 프로퍼티 이름과 동일해야 합니다

🔘 만약 인스턴스를 인코딩/디코딩할 때 없을 것 같은 프로퍼티에 대한 case는 생략하면 됩니다
대신, 생략된 프로퍼티는 default value를 가져야 합니다
(그래야 당신의 custom type이 Codable 프로토콜 자동 준수가 작동합니다)

🔘 만약 JSON에서의 key가 custom type의 프로퍼티 이름과 일치하지 않는다면,
CodingKeys 열거형에 String raw-value를 정의하여 대체 key를 제공하면 됩니다
당신이 각 case에 할당한 String raw-value는 인코딩/디코딩에서 key로 사용됩니다
(String raw-value : JSON에서의 key name)

case 이름과 raw-value 간의 연계로 custom type이 JSON key name을 그대로 사용하지 않고 Swift API Design Guidelines를 따르도록 만들 수 있게 됩니다

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    var vantagePoints: [Coordinate]
    
    enum CodingKeys: String, CodingKey {
        case name = "title"
        case foundingYear = "founding_date"
        
        case location
        case vantagePoints
    }
}

✔️ 수동으로 인코딩/디코딩 로직 구현하기

custom type을 그대로 인코딩하지 않고 구조를 변경하여 인코딩하고 싶다면, 인코딩/디코딩 로직을 직접 구현할 수도 있습니다

아래 예제에서는
JSON에서 nested 구조인 additionalInfo를 풀기 위해 별도의 CodingKey를 선언합니다

let json = """
{
    "latitude": 1.0,
    "longitude": 1.0,
    "additionalInfo": {
        "elevation": 1.0
    }
}
"""

struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double

    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case additionalInfo
    }
    
    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

🔘 커스텀 디코딩: init(from:) 구현

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)
        
        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}

🔘 커스텀 인코딩: encode(to:) 구현

extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
        
        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        try additionalInfo.encode(elevation, forKey: .elevation)
    }
}
profile
노션으로 이사갑니다 https://tungsten-run-778.notion.site/Study-Archive-98e51c3793684d428070695d5722d1fe

0개의 댓글

Powered by GraphCDN, the GraphQL CDN