빙수왕의 개발일지

KeyedDecodingContainer의 메소드들 - decode, nestedContainer, superDecoder 등 본문

개발/iOS

KeyedDecodingContainer의 메소드들 - decode, nestedContainer, superDecoder 등

빙수킹 2023. 1. 14. 18:58

KeyedDecodingContainer 주요 메소드들

  • decode(_:forKey:)
  • decodeIfPresent(_:forKey:)
  • decodeNil(forKey:)
  • nestedContainer(keyedBy:forKey:)
  • nestedUnkeyedContainer(forKey:)
  • superDecoder()
  • superDecoder(forKey:)

그 외 - configuration을 받는 메소드들

  • decode(forKey:configuration:)

음.. 자세히는 안봤지만,

decode 할 때 configuration 객체를 내가 커스텀해서 만들고 그걸 사용해서 decode할 수 있도록 해주는 메소드인 것 같다.

https://www.andyibanez.com/posts/the-mysterious-codablewithconfiguration-protocol/ 여기 보면 자세한 내용 있음


1. decode vs decodeIfPresent

Struct에 Decodable을 준수하면 기본으로 만들어주는 init(from:) 에서,

각 프로퍼티가 Optional인 경우는 decodeIfPresent 를 사용하고 아닌 경우는 decode 를 사용한다.

decode

  • 리턴타입이 non Optional
  • 해당 key나 value가 없으면 DecodingError.KeyNotFound , DecodingError.valueNotFound 와 같은 에러가 발생하면서 해당 객체의 Decoding에 실패한다.

decodeIfPresent

  • 리턴타입이 Optional
  • 해당 키가 없어도 DecodingError.KeyNotFound 가 발생하지 않는다. 대신 nil을 리턴한다.
  • 프로퍼티를 Optional로 만들고 싶지 않은 경우에는, default value를 제공하면 된다.

예시1 - 각 Optional, nonOptional 프로퍼티에서 사용되는 함수들

struct Coffee: Decodable {

    let title: String?
    let description: String
    let ingredients: [String]
    let image: String
    let id: Int

    enum CodingKeys: CodingKey {
        case title
        case description
        case ingredients
        case image
        case id
    }

    // Default init..
    // decode: 키에 해당하는 값이 없으면 Decoding Error가 발생하고 파싱에 실패한다.
    // 키가 여기 없는데 서버에서 추가로 내려오는건 상관 없다.
    // 프로퍼티들 중 Optional인 아이들은 기본 init에서 decodeIfPresent로 구현된다.
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        self.title = try container.decodeIfPresent(String.self, forKey: .title)
        self.description = try container.decode(String.self, forKey: .description)
        self.ingredients = try container.decode([String].self, forKey: .ingredients)
        self.image = try container.decode(String.self, forKey: .image)
        self.id = try container.decode(Int.self, forKey: .id)
    }
}

예시2 - 각 프로퍼티를 Optional이 아니면서 디코딩도 성공하도록 만들기

아래와 같이 default value를 제공하면 된다.

// 서버에서 어떻게 내려올지 확실하지 않다면..? 값이 없어도 파싱에 성공하고싶다면?
// 해당하는 키가 없어도 Decoding Error가 발생하지 않게 하고싶다면?
// decodeIfPresent 사용하면 된다.
// 대신 decodeIfPresent의 리턴형은 Optional이므로, 각 property들을 Optional로 지정하거나 기본값을 세팅해줘야 한다.
init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    self.title = try container.decodeIfPresent(String.self, forKey: .title) ?? "no title"
    self.description = try container.decodeIfPresent(String.self, forKey: .description) ?? "no description"
    self.ingredients = try container.decodeIfPresent([String].self, forKey: .ingredients) ?? []
    self.image = try container.decodeIfPresent(String.self, forKey: .image) ?? ""
    self.id = try container.decodeIfPresent(Int.self, forKey: .id) ?? -1
}

2. decodeNil(forKey:)

nil이면 true, 아니면 false 값 리턴


3. nestedContainer

말 그대로 nested - 현재 struct의 한 껍질 안의 값을 꺼내고 싶을 때 사용

예시

json 이 다음과 같고, 가장 밖의 struct에서 result.name을 바로 넣고싶으면?

{
    "result": {
        "name": "Lina"
    }
} 

다음과 같이 nestedContainer을 사용하면 된다.

struct MyResult: Decodable {
    public var name: String?

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let result = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .result)
        self.name = try result.decodeIfPresent(String.self, forKey: .name)
    }

    enum CodingKeys: CodingKey {
        case result
        case name
    }
}

4. superDecoder

container에서 super 키를 가진 아이의 decoder을 반환한다.

사용해본 적은 없는데..

찾아보니 Decodable 한 SuperClass를 상속받은 SubClass에서 사용하는 것 같다.

SubClass의 init(from:) 에서 super.init(from:) 을 불러주기 위한 용도..?

예시

class FullSub: FullSuper {

    var string: String?
    private enum CodingKeys: String, CodingKey { case string }

    override init() { super.init() }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // super.init에 사용하기 위한 super의 decoder
        let superdecoder = try container.superDecoder()
        try super.init(from: superdecoder)

        string = try container.decode(String.self, forKey: .string)
    }
}

class FullSuper: Codable {

    var id: UUID?

    init() {}

    private enum CodingKeys: String, CodingKey { case id }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
    }
}