빙수왕의 개발일지

Decoder 3가지 메소드 정리 (Container 종류) 본문

개발/iOS

Decoder 3가지 메소드 정리 (Container 종류)

빙수킹 2023. 1. 14. 17:53

Decoder의 메소드들 (Container 3가지)

Apple Developer Documentation

  • func container(keyedBy: Key.Type) throws -> KeyedDecodingContainer
  • func singleValueContainer() throws -> SingleValueDecodingContainer
  • func unkeyedContainer() throws -> UnkeyedDecodingContainer

1. KeyedDecodingContainer

  • CodingKey로 넘긴 키타입에 맞는 키를 가지는 값들을 포함하는 컨테이너.
  • 일반적인 key: value 형태의 json을 디코딩할 때 사용.
  • 우리가 일반적으로 property를 몇개 가진 struct에 Decodable을 준수시키면 init에서 이 형태의 Container가 사용된다.

예시

https://api.sampleapis.com/coffee/hot

위 API 아웃풋인 json을 디코딩한다고 가정하자.

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
    }

    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. SingleValueDecodingContainer

  • a single primitive value 을 가지는 Container.
  • key, value 없이, 값 1개(통으로) 때만 사용하는 아이
  • 값 1개라는건 예를들어 String 1개도 되겠지만, 1개의 array도 된다.

예시1

가장 상위의 껍질 struct가 불필요할 때 통 container를 만들어서 내부를 decode하면 좋을 것 같다.
ex) 1개의 array가 들어있는 struct

struct ResultList: Decodable {
    let results: [SomeResult]

    init(from decoder: Decoder) throws {
        results = try decoder.singleValueContainer().decode([SomeResult].self)
    }
}

struct SomeResult: Decodable {

}

예시2

각 최종 primitive type 에서 케이스를 나눠서 self에 넣어주고 싶을 때

ex) enum의 init에서 singleValueContainer 사용

internal enum Animal: String, Decodable {
    case dog = "DOG"
    case cat = "CAT"
    case unknown

    internal init(from decoder: Decoder) throws {
        if let rawValue = try? decoder.singleValueContainer().decode(RawValue.self),
           let animal = Animal(rawValue: rawValue) {
            self = animal
        } else {
            self = .unknown
        }
    }
}

3. UnkeyedDecodingContainer

  • key가 없는 값들을 보유하기에 적합한 Container
  • 순서대로 decode 된다.

예시

아래와 같은 key가 없는 array 형태의 json 파일을 디코딩 할 때 적합

["a", "b", 1, 3, 0.3]

특이사항: decode 함수가 mutable 이다!!

struct ArrayDecoder: Decodable {

    let a: String
    let b: String
    let c: Int?
    let d: Bool
    let e: Double

    enum CodingKeys: CodingKey {
        case a
        case b
        case c
        case d
        case e
    }

    init(from decoder: Decoder) throws {

        // decode 함수를 부르면 var로 바꾸라고 한다.
        // 다른 아이들과 다르게 decode 함수가 mutating이다.
        // 그 이유는 decode하면 그 아이가 array에서 pop(?)되기 때문
        var container = try decoder.unkeyedContainer()
        self.a = try container.decode(String.self)
        self.b = try container.decode(String.self)

        // decodeIfPresent 에서 실패해서 nil이 나오면 pop되지 않는다.
        self.c = try container.decodeIfPresent(Int.self)

        // 값이 nil이면 true, 아니면 false
        self.d = try container.decodeNil()

        self.e = try container.decode(Double.self)
    }
}