Réponses
Trop de publicités?Comment faire des énumérations associés à des types conformes à l' Codable
Cette réponse est similaire à @Howard Lovatt mais évite de créer un PostTypeCodableForm
struct et utilise à la place de l' KeyedEncodingContainer
type fourni par Apple comme une propriété sur Encoder
et Decoder
, ce qui réduit réutilisable.
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)
}
}
}
Ce code fonctionne pour moi sur 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)
Swift serait jeter un .dataCorrupted
d'erreur s'il rencontre des inconnus valeur d'enum. Si vos données sont à venir à partir d'un serveur, il peut vous envoyer un inconnu de la valeur d'enum à tout moment (bug côté serveur, ce nouveau type de ajouté dans une version de l'API et vous voulez que les précédentes versions de votre application pour gérer le cas gracieusement, etc), vous feriez mieux d'être préparé, et le code "style défensif" en toute sécurité décoder vos énumérations.
Voici un exemple sur la façon de le faire, avec ou sans valeur
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)
}
}
}
Et comment l'utiliser dans un enfermant struct:
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)
}
}
Pour prolonger sur @Toka réponse, vous pouvez aussi ajouter un raw représentable valeur de l'enum, et utiliser la valeur par défaut option constructeur pour construire l'enum sans 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
}
}
Il peut être étendu à l'aide d'un protocole personnalisé qui permet de refactoriser le constructeur:
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
}
Il peut également être facilement étendu pour lancer une erreur si une défaillance de valeur d'énumération a été spécifié, plutôt que de faire défaut sur une valeur. Gist avec ce changement est disponible ici: https://gist.github.com/stephanecopin/4283175fabf6f0cdaf87fef2a00c8128.
Le code a été compilé et testé à l'aide de Swift 4.1/Xcode 9.3.