2017-06-15 18 views
21

Swift 4 dodał nowy protokół Codeable. Kiedy używam JSONDecoder wydaje się wymagać wszystkich nie-opcjonalnych właściwości mojej klasy Codeable, aby mieć klucze w JSON lub zgłasza błąd.Z JSONDecoder w Swift 4, czy brakujące klucze mogą używać wartości domyślnej, zamiast być właściwościami opcjonalnymi?

Dokonywanie każdej właściwości mojej klasy opcjonalnej wydaje się niepotrzebnym kłopotem, ponieważ to, czego naprawdę chcę, to użyć wartości w jsonie lub wartości domyślnej. (Nie chcę, aby nieruchomość była zerowa).

Czy jest jakiś sposób na zrobienie tego?

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

Jeden więcej zapytań co czy mogę zrobić, jeśli mam wiele kluczy w moim jsonie i chcę napisać ogólną metodę odwzorowania jsona na obiekt, zamiast podawania go zeru Domyślna wartość co najmniej. –

Odpowiedz

29

Można zaimplementować metodę w swoim rodzaju init(from decoder: Decoder) zamiast używania realizację domyślnie:

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

Można również dokonać name stałą właściwość (jeśli chcesz):

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

lub

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 Twój komentarz: z rozszerzeniem niestandardowych

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

można wdrożyć metodę init, jak

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

ale to nie jest dużo krótszy niż

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

Należy również zauważyć, że w tym konkretnym przypadku można użyć automatycznie wygenerowanego wyliczenia "CodingKeys' (tak, aby usunąć definicję niestandardową) :) – Hamish

+0

@Hamish: Nie skompilowałem się, kiedy po raz pierwszy wypróbowałem, ale teraz działa :) –

+0

Tak, obecnie jest trochę niejednolita, ale zostanie naprawiona (https://bugs.swift.org/browse/SR-5215) – Hamish

Powiązane problemy