Hızlı 4'te bir numaralandırmayı nasıl çözülebilir yapabilirim?


157
enum PostType: Decodable {

    init(from decoder: Decoder) throws {

        // What do i put here?
    }

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

Bunu tamamlamak için ne koyayım? Ayrıca, diyelim ki bunu değiştirdim case:

case image(value: Int)

Bunu Decodable'a nasıl uygun hale getirebilirim?

EDit İşte tam kodum (çalışmıyor)

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
}

Son Düzenleme Ayrıca böyle bir numaralandırmayı nasıl ele alacaktır?

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

Yanıtlar:


262

Oldukça kolay, sadece kullanacak Stringveya Intdolaylı atanır ham değerler.

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

imageiçin kodlanmış 0ve blobhiç1

Veya

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

imageiçin kodlanmış "image"ve blobhiç"blob"


Bu nasıl kullanılacağına dair basit bir örnektir:

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
önerdiğiniz kodu denedim, ama çalışmıyor. Kodunu çözmek için çalışıyorum JSON göstermek için düzenledim
hızlı nub

8
Bir enum yalnızca kodlanamaz / çözülemez. Bir yapıya gömülmelidir. Bir örnek ekledim.
vadian

Bunu doğru olarak işaretleyeceğim. Ancak yukarıdaki soruda cevaplanmayan son bir parçası vardı. Numaram böyle görünüyorsa ne olacak? (yukarıda düzenlendi)
swift nub

İlişkili türlerde numaralandırma kullanıyorsanız, özel kodlama ve kod çözme yöntemleri yazmanız gerekir. Lütfen Kodlama ve Kod Çözme Özel Türlerini
vadian

1
Hakkında "Bir enum tr olamaz / sadece deşifre.", En çözülecek gibi görünüyor iOS 13.3. Test ediyorum iOS 13.3ve iOS 12.4.3farklı davranıyorlar. Altında iOS 13.3, enum yalnızca kodlanabilir / çözülebilir.
AechoLiu

111

İlişkili türlere sahip numaralandırmalar Codable

Bu cevap @Howard Lovatt en benzer ancak bir yaratma önler PostTypeCodableFormyapı ve bunun yerine kullandığı KeyedEncodingContainertürü Apple tarafından sağlanan bir özelliği olarak Encoderve Decoderklişe azaltır.

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

Bu kod benim için Xcode 9b3 üzerinde çalışıyor.

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)

Bu cevabı seviyorum! Bir not olarak, bu örnek de yankılandı yapma konusunda objc.io bir yazı Eitherkodlanabilir
Ben Leggiero

En iyi cevap
Peter Suwara

38

Swift, .dataCorruptedbilinmeyen numaralandırma değeriyle karşılaşırsa bir hata atar . Verileriniz bir sunucudan geliyorsa, istediğiniz zaman size bilinmeyen bir numaralandırma değeri gönderebilir (hata sunucusu tarafı, bir API sürümüne yeni tür eklendi ve uygulamanızın önceki sürümlerinin davayı zarif bir şekilde işlemesini vb.), hazırlıklı olsan iyi olur ve "savunma tarzını" kodla.

Aşağıda, ilişkili değerle veya ilişkili değer olmadan nasıl yapılacağıyla ilgili bir örnek

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

Ve onu bir kapalı yapıda nasıl kullanabilirsiniz:

    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
Teşekkürler, cevabınızı anlamak çok daha kolay.
DazChong

1
Bu cevap da bana yardımcı oldu, teşekkürler.
Enum'unuzun

27

@ Toka'nın cevabını genişletmek için, numaralandırmaya ham temsili bir değer ekleyebilir ve numaralandırmayı aşağıdakiler olmadan oluşturmak için varsayılan isteğe bağlı kurucuyu kullanabilirsiniz 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
  }
}

Yapıcıyı yeniden düzenlemeye izin veren özel bir protokol kullanılarak genişletilebilir:

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
}

Geçersiz bir numaralandırma değeri belirtilmişse, bir değeri varsayılan yapmak yerine hata atmak için kolayca genişletilebilir. Bu değişiklikle ilgili bilgileri burada bulabilirsiniz: https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128 .
Kod Swift 4.1 / Xcode 9.3 kullanılarak derlendi ve test edildi.


1
Aradığım cevap bu.
Nathan Hosselton

7

@ Proxpero'nun ters olan yanıtının bir varyantı, dekoderi şu şekilde formüle etmek olacaktır:

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

Bu, derleyicinin vakaları kapsamlı bir şekilde doğrulamasına izin verir ve kodlanan değerin anahtarın beklenen değeriyle eşleşmediği durumda hata iletisini bastırmaz.


Bunun daha iyi olduğuna katılıyorum.
proxpero

6

Aslında yukarıdaki cevaplar gerçekten harika, ancak sürekli olarak geliştirilen bir istemci / sunucu projesinde birçok insanın neye ihtiyacı olduğuna dair bazı ayrıntılar eksik. Arka ucumuz zaman içinde sürekli olarak gelişirken bir uygulama geliştiriyoruz, bu da bazı numaralandırma vakalarının bu evrimi değiştireceği anlamına geliyor. Bu nedenle, bilinmeyen vakalar içeren numaralandırma dizilerinin kodunu çözebilen bir enum kod çözme stratejisine ihtiyacımız var. Aksi takdirde diziyi içeren nesnenin kodunun çözülmesi başarısız olur.

Yaptığım şey oldukça basit:

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: Uygulamayı gizle> Koleksiyon haline getir

Uygulama detaylarını gizlemek her zaman iyi bir fikirdir. Bunun için biraz daha fazla koda ihtiyacınız olacak. Hüner uyması olduğu DirectionsListiçin Collectionve iç hale listdizi özel:

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'in bu blog gönderisinde özel koleksiyonlara uyma hakkında daha fazla bilgi edinebilirsiniz: https://medium.com/@johnsundell/creating-custom-collections-in-swift-a344e25d0bb0


5

İstediğinizi yapabilirsiniz, ancak biraz dahil :(

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)

ilginç kesmek
Roman Filippov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.