Как сделать перечисление Декодируемым в swift 4?



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)


как я могу сделать это в соответствии с Декодируемым ?



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

7 ответов:

это довольно легко, просто использовать String или Int необработанные значения, которые неявно назначаются.

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

image кодируется 0 и blob до 1

или

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 struct и вместо этого использует KeyedEncodingContainer тип предоставлено Apple в собственность на 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 ошибка при обнаружении неизвестного значения enum. Если ваши данные поступают с сервера, он может отправить Вам неизвестное значение enum в любое время (ошибка на стороне сервера, новый тип, добавленный в версию 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.

вы можете делать то, что хотите, но это немного связано : (

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)

вариант ответа @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)
    }
}

это позволяет компилятору полностью проверить случаи, а также не подавляет сообщение об ошибке для случая, когда закодированное значение не соответствует ожидаемому значению ключа.

на самом деле ответы выше велики, но не было достаточно для того, что нам нужно сегодня. Мы разрабатываем приложение, в то время как наш бэкэнд расширяется с течением времени. Это означает, что наши случаи перечисления также будут расширяться. Поэтому нам нужна стратегия декодирования перечислений, которая гарантирует, что старые приложения могут декодировать списки перечислений, которые содержат неизвестные случаи. Без этого декодирование всего объекта, который содержит список перечислений просто терпит неудачу.

что я сделал довольно просто:

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

public struct DirectionList {

    public 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
            let value = try container.decode(String.self)

            guard let direction = Direction(rawValue: value) else {
                // Unknown enum value found - ignore
                continue
            }
            // Add all known enum cases to the list of directions
            directions.append(direction)
        }
        self.directions = directions
    }
}

Бонус: Скрыть реализация > сделать его коллекцией

скрыть детали реализации всегда хорошая идея. Для этого вам понадобится всего лишь немного больше кода. Фокус в том, чтобы соответствовать DirectionsList до Collection и сделать свой внутренний list массив 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)
    }
}

вы можете прочитать больше о соответствии пользовательским коллекциям в этом блоге Джона Санделла: https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0

Comments

    Ничего не найдено.