/ / Якщо JSONDecoder у Swift 4, чи можуть відсутні ключі використовувати значення за замовчуванням замість того, щоб бути необов’язковими властивостями? - json, swift, swift4, codable

За допомогою JSONDecoder у Swift 4, можуть відсутні клавіші використовувати значення за замовчуванням, а не мати необов'язкові властивості? - json, swift, swift4, кодований

Swift 4 додав нове Codeable протокол. Коли я використовую JSONDecoder здається, потрібні всі необов'язкові властивості мого Codeable класу, щоб мати ключі в JSON або він видає помилку.

Зробити будь-яке властивість мого класу необов’язковим здається непотрібним клопотом, оскільки те, що я дійсно хочу, - це використовувати значення в json або значення за замовчуванням. (Я не хочу, щоб майно було нульовим.)

Чи є спосіб зробити це?

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

Відповіді:

44 за відповідь № 1

Ви можете реалізувати init(from decoder: Decoder) метод вашого типу замість використання типової реалізації:

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

Ви також можете зробити name постійне властивість (якщо ви хочете):

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

або

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

До вашого коментаря: Із спеціальним розширенням

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

ви можете реалізувати метод init як

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

але це не набагато коротше, ніж

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