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


157
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)
}

답변:


262

암시 적으로 할당 된 사용 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)
}

1
제안한 코드를 시도했지만 작동하지 않습니다. 나는 JSON 내가 디코딩을 시도하고 보여 내 코드를 편집 한
신속을 덩어리

8
열거 형은 단독으로 인코딩 / 디코딩 할 수 없습니다. 구조체에 포함되어야합니다. 예를 추가했습니다.
vadian

이것을 올바른 것으로 표시하겠습니다. 그러나 위의 질문에서 마지막 부분은 답변되지 않았습니다. 내 열거 형이 이와 같이 보이면 어떻게됩니까? (위에서 편집)
swift nub

연결된 형식으로 열거 형을 사용하는 경우 사용자 지정 인코딩 및 디코딩 방법을 작성해야합니다. 읽어주십시오 인코딩 및 디코딩 사용자 정의 유형
vadian

1
"열거 형은 단독으로 인코딩 / 디코딩 할 수 없습니다."에 대해서는에서 해결 된 것으로 보입니다 iOS 13.3. 내가 테스트 iOS 13.3하고 iOS 12.4.3, 그들은 다르게 동작합니다. 에서 iOS 13.3enum은 단독으로 인코딩 / 디코딩 할 수 있습니다.
AechoLiu

111

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

이 답변은 @Howard Lovatt와 비슷하지만 PostTypeCodableForm구조체 생성을 피하고 대신 Apple 에서 제공 하는 KeyedEncodingContainer유형 을 및 의 속성으로 사용하여 상용구를 줄입니다.EncoderDecoder

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)

나는이 대답을 좋아한다! 참고로,이 예제도에 반향되어 만드는 방법에 대한 objc.io에 게시물 Eithercodable
벤 Leggiero

가장 좋은 답변
Peter Suwara

38

.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)
      }
   }

1
감사합니다, 당신의 대답은 이해하기 훨씬 쉽습니다.
DazChong

1
이 답변은 나에게도 도움이되었습니다. 감사합니다. 열거 형을 String에서 상속함으로써 향상시킬 수 있습니다. 그러면 문자열을 바꿀 필요가 없습니다
Gobe

27

@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을 사용하여 컴파일 및 테스트되었습니다.


1
이것이 내가 찾은 대답입니다.
Nathan Hosselton

7

더 정확한 @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)
    }
}

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


나는 이것이 더 낫다는 것에 동의합니다.
proxpero

6

실제로 위의 답변은 정말 훌륭하지만 지속적으로 개발되는 클라이언트 / 서버 프로젝트에서 많은 사람들이 필요로하는 세부 사항이 누락되었습니다. 우리는 백엔드가 시간이 지남에 따라 지속적으로 진화하는 동안 앱을 개발합니다. 이는 일부 열거 형 사례가 그 진화를 바꿀 것임을 의미합니다. 따라서 알려지지 않은 경우를 포함하는 열거 형 배열을 디코딩 할 수있는 열거 형 디코딩 전략이 필요합니다. 그렇지 않으면 배열이 포함 된 객체의 디코딩이 실패합니다.

내가 한 일은 매우 간단합니다.

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
    }
}

보너스 : 구현 숨기기> 컬렉션으로 만들기

구현 세부 사항을 숨기는 것은 항상 좋은 생각입니다. 이를 위해서는 약간 더 많은 코드가 필요합니다. 비결은 내부 배열 을 준수 DirectionsList하고 비공개로 Collection만드는 것입니다 list.

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)
    }
}

John Sundell의이 블로그 게시물 ( https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0) 에서 사용자 지정 컬렉션 준수에 대한 자세한 내용을 확인할 수 있습니다.


5

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

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)

흥미로운 해킹
로마 Filippov
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.