С JSONDecoder в Swift 4, могут ли отсутствующие ключи использовать значение по умолчанию вместо того, чтобы быть необязательными свойствами?
Swift 4 добавил новый Codeable
протокол. Когда я использую JSONDecoder
похоже, для этого требуются все необязательные свойства my 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
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"
}
Re ваш комментарий: с пользовательским расширением
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"