zek*_*kel 88 json swift swift4 codable
Swift 4添加了新Codable
协议.当我使用JSONDecoder
它似乎要求我的Codable
类的所有非可选属性在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
Run Code Online (Sandbox Code Playgroud)
Mar*_*n R 105
您可以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
}
}
}
Run Code Online (Sandbox Code Playgroud)
您还可以创建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"
}
}
}
Run Code Online (Sandbox Code Playgroud)
要么
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"
}
Run Code Online (Sandbox Code Playgroud)
重新评论:使用自定义扩展程序
extension KeyedDecodingContainer {
func decodeWrapper<T>(key: K, defaultValue: T) throws -> T
where T : Decodable {
return try decodeIfPresent(T.self, forKey: key) ?? defaultValue
}
}
Run Code Online (Sandbox Code Playgroud)
你可以实现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")
}
Run Code Online (Sandbox Code Playgroud)
但这并不比它短得多
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Default Appleseed"
Run Code Online (Sandbox Code Playgroud)
小智 18
你可以实施。
struct Source : Codable {
let id : String?
let name : String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? ""
name = try values.decodeIfPresent(String.self, forKey: .name)
}
}
Run Code Online (Sandbox Code Playgroud)
Cri*_*tik 16
一种解决方案是,如果未找到JSON密钥,则使用默认为所需值的计算属性。这将增加一些额外的冗长性,因为您需要声明另一个属性,并且需要添加CodingKeys
枚举(如果尚未存在)。优点是您无需编写自定义解码/编码代码。
例如:
class MyCodable: Codable {
var name: String { return _name ?? "Default Appleseed" }
var age: Int?
private var _name: String?
enum CodingKeys: String, CodingKey {
case _name = "name"
case age
}
}
Run Code Online (Sandbox Code Playgroud)
我更喜欢的方法是使用所谓的DTO-数据传输对象。它是一个结构,符合Codable并表示所需的对象。
struct MyClassDTO: Codable {
let items: [String]?
let otherVar: Int?
}
Run Code Online (Sandbox Code Playgroud)
然后,您只需使用该DTO初始化要在应用程序中使用的对象。
class MyClass {
let items: [String]
var otherVar = 3
init(_ dto: MyClassDTO) {
items = dto.items ?? [String]()
otherVar = dto.otherVar ?? 3
}
var dto: MyClassDTO {
return MyClassDTO(items: items, otherVar: otherVar)
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法也是很好的,因为您可以按自己的意愿重命名和更改最终对象。很明显,比手动解码需要更少的代码。此外,通过这种方法,您可以将网络层与其他应用程序分开。
我遇到了这个问题,正在寻找完全相同的东西。尽管我担心这里的解决方案是唯一的选择,但我找到的答案并不是很令人满意。
就我而言,创建自定义解码器需要大量难以维护的样板文件,因此我一直在寻找其他答案。
我遇到了这篇文章,它展示了一种有趣的方法,可以在简单的情况下使用@propertyWrapper
. 对我来说最重要的是它是可重用的,并且需要对现有代码进行最少的重构。
本文假设您希望缺少的布尔属性默认为 false 而不会失败,但也显示其他不同的变体。您可以更详细地阅读它,但我将展示我为我的用例所做的工作。
就我而言,array
如果缺少密钥,我希望将其初始化为空。
因此,我声明了以下@propertyWrapper
和其他扩展:
@propertyWrapper
struct DefaultEmptyArray<T:Codable> {
var wrappedValue: [T] = []
}
//codable extension to encode/decode the wrapped value
extension DefaultEmptyArray: Codable {
func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode([T].self)
}
}
extension KeyedDecodingContainer {
func decode<T:Decodable>(_ type: DefaultEmptyArray<T>.Type,
forKey key: Key) throws -> DefaultEmptyArray<T> {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法的优点是您可以通过简单地向@propertyWrapper
属性添加 来轻松克服现有代码中的问题。就我而言:
@DefaultEmptyArray var items: [String] = []
Run Code Online (Sandbox Code Playgroud)
希望这有助于有人处理同样的问题。
在继续调查此事的同时发布此答案后,我发现了另一篇文章,但最重要的是,相应的库包含一些常见的易于使用@propertyWrapper
的此类情况:
https://github.com/marksands/BetterCodable
归档时间: |
|
查看次数: |
30121 次 |
最近记录: |