2017-06-15 7 views
21

Swift 4 hinzugefügt das neue Codeable Protokoll. Wenn ich JSONDecoder verwende, scheint es, dass alle nicht-optionalen Eigenschaften meiner Klasse Codeable Schlüssel im JSON benötigen oder einen Fehler auslösen.Mit JSONDecoder in Swift 4, können fehlende Schlüssel einen Standardwert verwenden, anstatt optionale Eigenschaften zu sein?

Jede Eigenschaft meiner Klasse optional zu machen scheint eine unnötige Hektik zu sein, da das, was ich wirklich will, ist, den Wert in der JSON oder einen Standardwert zu verwenden. (Ich möchte nicht, dass die Eigenschaft null ist.)

Gibt es eine Möglichkeit, dies zu tun?

class MyCodable: Codable { 
    var name: String = "Default Appleseed" 
} 

func load(input: String) { 
    do { 
     if let data = input.data(using: .utf8) { 
      let result = try JSONDecoder().decode(MyCodable.self, from: data) 
      print("name: \(result.name)") 
     } 
    } catch { 
     print("error: \(error)") 
     // `Error message: "Key not found when expecting non-optional type 
     // String for coding key \"name\""` 
    } 
} 

let goodInput = "{\"name\": \"Jonny Appleseed\" }" 
let badInput = "{}" 
load(input: goodInput) // works, `name` is Jonny Applessed 
load(input: badInput) // breaks, `name` required since property is non-optional 
+0

Eine weitere Abfrage, was kann ich tun, wenn ich mehrere Schlüssel in meinem JSON habe und ich eine generische Methode schreiben möchte, um JSON zuzuordnen, um ein Objekt zu erstellen, anstatt nil zu geben, sollte es g ive Standardwert atleast. –

Antwort

29

Sie können die init(from decoder: Decoder) Methode in der Art umzusetzen, anstatt die Standardimplementierung:

class MyCodable: Codable { 
    var name: String = "Default Appleseed" 

    required init(from decoder: Decoder) throws { 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     if let name = try container.decodeIfPresent(String.self, forKey: .name) { 
      self.name = name 
     } 
    } 
} 

Sie können auch name eine konstante Eigenschaft machen (wenn Sie wollen):

class MyCodable: Codable { 
    let name: String 

    required init(from decoder: Decoder) throws { 
     let container = try decoder.container(keyedBy: CodingKeys.self) 
     if let name = try container.decodeIfPresent(String.self, forKey: .name) { 
      self.name = name 
     } else { 
      self.name = "Default Appleseed" 
     } 
    } 
} 

oder

required init(from decoder: Decoder) throws { 
    let container = try decoder.container(keyedBy: CodingKeys.self) 
    self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed" 
} 

Re Ihr Kommentar: Mit einer benutzerdefinierten Erweiterung

extension KeyedDecodingContainer { 
    func decodeWrapper<T>(key: K, defaultValue: T) throws -> T 
     where T : Decodable { 
     return try decodeIfPresent(T.self, forKey: key) ?? defaultValue 
    } 
} 

Sie die init-Methode als

required init(from decoder: Decoder) throws { 
    let container = try decoder.container(keyedBy: CodingKeys.self) 
    self.name = try container.decodeWrapper(key: .name, defaultValue: "Default Appleseed") 
} 

implementieren könnte, aber das ist nicht viel kürzer als

self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed" 
+0

Beachten Sie auch, dass Sie in diesem speziellen Fall die automatisch generierte 'CodingKeys'-Enumeration verwenden können (so kann die benutzerdefinierte Definition entfernt werden) :) – Hamish

+0

@Hamish: Es wurde nicht kompiliert, als ich es zum ersten Mal versuchte, aber jetzt funktioniert es :) –

+0

Ja, es ist derzeit ein wenig lückenhaft, aber wird behoben werden (https://bugs.swift.org/browse/SR-5215) – Hamish

Verwandte Themen