program story

스위프트 4에서 열거 형 Decodable을 어떻게 만듭니 까?

inputbox 2020. 8. 3. 08:23
반응형

스위프트 4에서 열거 형 Decodable을 어떻게 만듭니 까?


enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

    case Image
    enum CodingKeys: String, CodingKey {
        case image
    }
}

이것을 완료하기 위해 무엇을 넣어야합니까? 또한 내가 case이것을 다음과 같이 변경했다고 가정 해 봅시다 .

case image(value: Int)

이것을 Decodable에 맞추려면 어떻게해야합니까?

EDit 여기 내 코드가 있습니다 (작동하지 않습니다)

let jsonData = """
{
    "count": 4
}
""".data(using: .utf8)!

        do {
            let decoder = JSONDecoder()
            let response = try decoder.decode(PostType.self, from: jsonData)

            print(response)
        } catch {
            print(error)
        }
    }
}

enum PostType: Int, Codable {
    case count = 4
}

최종 편집 또한 이와 같은 열거 형을 어떻게 처리합니까?

enum PostType: Decodable {
    case count(number: Int)
}

암시 적으로 할당 된 사용 String또는 Int원시 값은 매우 쉽습니다 .

enum PostType: Int, Codable {
    case image, blob
}

image로 부호화 0blob1

또는

enum PostType: String, Codable {
    case image, blob
}

image로 부호화 "image"blob"blob"


다음은 간단한 사용법입니다.

enum PostType : Int, Codable {
    case count = 4
}

struct Post : Codable {
    var type : PostType
}

let jsonString = "{\"type\": 4}"

let jsonData = Data(jsonString.utf8)

do {
    let decoded = try JSONDecoder().decode(Post.self, from: jsonData)
    print("decoded:", decoded.type)
} catch {
    print(error)
}

관련된 형식의 열거 형을 준수하는 방법 Codable

이 답변 @Howard LOVATT의과 유사하지만 생성 방지 PostTypeCodableForm구조체를 대신 사용 KeyedEncodingContainer유형에 애플에 의해 제공 에 속성으로를 Encoder하고 Decoder보일러를 감소시킨다.

enum PostType: Codable {
    case count(number: Int)
    case title(String)
}

extension PostType {

    private enum CodingKeys: String, CodingKey {
        case count
        case title
    }

    enum PostTypeCodingError: Error {
        case decoding(String)
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        if let value = try? values.decode(Int.self, forKey: .count) {
            self = .count(number: value)
            return
        }
        if let value = try? values.decode(String.self, forKey: .title) {
            self = .title(value)
            return
        }
        throw PostTypeCodingError.decoding("Whoops! \(dump(values))")
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
        case .count(let number):
            try container.encode(number, forKey: .count)
        case .title(let value):
            try container.encode(value, forKey: .title)
        }
    }
}

이 코드는 Xcode 9b3에서 작동합니다.

import Foundation // Needed for JSONEncoder/JSONDecoder

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()

let count = PostType.count(number: 42)
let countData = try encoder.encode(count)
let countJSON = String.init(data: countData, encoding: .utf8)!
print(countJSON)
//    {
//      "count" : 42
//    }

let decodedCount = try decoder.decode(PostType.self, from: countData)

let title = PostType.title("Hello, World!")
let titleData = try encoder.encode(title)
let titleJSON = String.init(data: titleData, encoding: .utf8)!
print(titleJSON)
//    {
//        "title": "Hello, World!"
//    }
let decodedTitle = try decoder.decode(PostType.self, from: titleData)

.dataCorrupted알 수없는 열거 형 값이 발생하면 Swift에서 오류가 발생합니다. 데이터가 서버에서 오는 경우 언제든지 알 수없는 열거 형 값을 보낼 수 있습니다 (버그 서버 측, API 버전에 추가 된 새 유형 및 이전 버전의 앱이 사례를 정상적으로 처리하기를 원함), 더 잘 준비하고 "방어 스타일"을 코딩하여 열거를 안전하게 해독하십시오.

다음은 관련 값이 있거나없는 방법입니다.

    enum MediaType: Decodable {
       case audio
       case multipleChoice
       case other
       // case other(String) -> we could also parametrise the enum like that

       init(from decoder: Decoder) throws {
          let label = try decoder.singleValueContainer().decode(String.self)
          switch label {
             case "AUDIO": self = .audio
             case "MULTIPLE_CHOICES": self = .multipleChoice
             default: self = .other
             // default: self = .other(label)
          }
       }
    }

그리고 그것을 둘러싸는 구조체에서 사용하는 방법 :

    struct Question {
       [...]
       let type: MediaType

       enum CodingKeys: String, CodingKey {
          [...]
          case type = "type"
       }


   extension Question: Decodable {
      init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
         [...]
         type = try container.decode(MediaType.self, forKey: .type)
      }
   }

@Toka의 답변을 확장하려면 너무 표현 할 수없는 원시 값을 열거 형에 추가하고 기본 선택적 생성자를 사용하여 switch: 없이 열거를 작성하십시오 .

enum MediaType: String, Decodable {
  case audio = "AUDIO"
  case multipleChoice = "MULTIPLE_CHOICES"
  case other

  init(from decoder: Decoder) throws {
    let label = try decoder.singleValueContainer().decode(String.self)
    self = MediaType(rawValue: label) ?? .other
  }
}

생성자를 리팩터링 할 수있는 사용자 정의 프로토콜을 사용하여 확장 할 수 있습니다.

protocol EnumDecodable: RawRepresentable, Decodable {
  static var defaultDecoderValue: Self { get }
}

extension EnumDecodable where RawValue: Decodable {
  init(from decoder: Decoder) throws {
    let value = try decoder.singleValueContainer().decode(RawValue.self)
    self = Self(rawValue: value) ?? Self.defaultDecoderValue
  }
}

enum MediaType: String, EnumDecodable {
  static let defaultDecoderValue: MediaType = .other

  case audio = "AUDIO"
  case multipleChoices = "MULTIPLE_CHOICES"
  case other
}

또한 값을 기본값으로 사용하지 않고 잘못된 열거 형 값을 지정한 경우 오류가 발생하도록 쉽게 확장 할 수 있습니다. 이 변경 사항이있는 요지는 https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128에서 제공 됩니다.
코드는 Swift 4.1 / Xcode 9.3을 사용하여 컴파일 및 테스트되었습니다.


더 정확한 @proxpero의 응답 변형은 다음과 같이 디코더를 공식화하는 것입니다.

public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    guard let key = values.allKeys.first else { throw err("No valid keys in: \(values)") }
    func dec<T: Decodable>() throws -> T { return try values.decode(T.self, forKey: key) }

    switch key {
    case .count: self = try .count(dec())
    case .title: self = try .title(dec())
    }
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    switch self {
    case .count(let x): try container.encode(x, forKey: .count)
    case .title(let x): try container.encode(x, forKey: .title)
    }
}

이를 통해 컴파일러는 사례를 철저하게 확인할 수 있으며 인코딩 된 값이 키의 예상 값과 일치하지 않는 경우 오류 메시지를 표시하지 않습니다.


원하는 것을 할 수 있지만 약간 관련이 있습니다. (

import Foundation

enum PostType: Codable {
    case count(number: Int)
    case comment(text: String)

    init(from decoder: Decoder) throws {
        self = try PostTypeCodableForm(from: decoder).enumForm()
    }

    func encode(to encoder: Encoder) throws {
        try PostTypeCodableForm(self).encode(to: encoder)
    }
}

struct PostTypeCodableForm: Codable {
    // All fields must be optional!
    var countNumber: Int?
    var commentText: String?

    init(_ enumForm: PostType) {
        switch enumForm {
        case .count(let number):
            countNumber = number
        case .comment(let text):
            commentText = text
        }
    }

    func enumForm() throws -> PostType {
        if let number = countNumber {
            guard commentText == nil else {
                throw DecodeError.moreThanOneEnumCase
            }
            return .count(number: number)
        }
        if let text = commentText {
            guard countNumber == nil else {
                throw DecodeError.moreThanOneEnumCase
            }
            return .comment(text: text)
        }
        throw DecodeError.noRecognizedContent
    }

    enum DecodeError: Error {
        case noRecognizedContent
        case moreThanOneEnumCase
    }
}

let test = PostType.count(number: 3)
let data = try JSONEncoder().encode(test)
let string = String(data: data, encoding: .utf8)!
print(string) // {"countNumber":3}
let result = try JSONDecoder().decode(PostType.self, from: data)
print(result) // count(3)

Actually the answers above are really great, but they are missing some details for what many people need in a continuously developed client/server project. We develop an app while our backend continually evolves over time, which means some enum cases will change that evolution. So we need an enum decoding strategy that is able to decode arrays of enums that contain unknown cases. Otherwise decoding the object that contains the array simply fails.

What I did is quite simple:

enum Direction: String, Decodable {
    case north, south, east, west
}

struct DirectionList {
   let directions: [Direction]
}

extension DirectionList: Decodable {

    public init(from decoder: Decoder) throws {

        var container = try decoder.unkeyedContainer()

        var directions: [Direction] = []

        while !container.isAtEnd {

            // Here we just decode the string from the JSON which always works as long as the array element is a string
            let rawValue = try container.decode(String.self)

            guard let direction = Direction(rawValue: rawValue) else {
                // Unknown enum value found - ignore, print error to console or log error to analytics service so you'll always know that there are apps out which cannot decode enum cases!
                continue
            }
            // Add all known enum cases to the list of directions
            directions.append(direction)
        }
        self.directions = directions
    }
}

Bonus: Hide implementation > Make it a Collection

To hide implementation detail is always a good idea. For this you'll need just a little bit more code. The trick is to conform DirectionsList to Collection and make your internal list array private:

struct DirectionList {

    typealias ArrayType = [Direction]

    private let directions: ArrayType
}

extension DirectionList: Collection {

    typealias Index = ArrayType.Index
    typealias Element = ArrayType.Element

    // The upper and lower bounds of the collection, used in iterations
    var startIndex: Index { return directions.startIndex }
    var endIndex: Index { return directions.endIndex }

    // Required subscript, based on a dictionary index
    subscript(index: Index) -> Element {
        get { return directions[index] }
    }

    // Method that returns the next index when iterating
    func index(after i: Index) -> Index {
        return directions.index(after: i)
    }
}

You can read more about conforming to custom collections in this blog post by John Sundell: https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0

참고URL : https://stackoverflow.com/questions/44580719/how-do-i-make-an-enum-decodable-in-swift-4

반응형