在回答之前:
我知道:
- 空字符串是无效
URL
- 我可以为
Employee
编写一个自定义解码器 - 我可以宣布
url
为String
我正在寻找的是解码可选URL
本身的更好解决方案。我希望我缺少一些Codable
魔法!
所以,我有这样的JSON。
let json = Data("""
{
"name": "Fred",
"url": ""
}
""".utf8)
以及包含可选 URL 的相应对象...
struct Employee: Decodable {
let name: String
let url: URL?
}
由于 JSON 中的url
无效,我希望它解码为nil
,而不是抛出错误。
尝试以下操作不起作用(它不会被调用(...
extension Optional where Wrapped == URL {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try container.decode(URL.self)
} catch {
self = nil
}
}
}
过去我使用过...
struct FailableDecodable<T: Decodable>: Deodable {
let wrapped: T?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self.wrapped = try container.decode(T.self)
} catch {
print("Error decoding failable object: (error)")
self.wrapped = nil
}
}
}
struct Employee: Decodable {
let name: String
let url: FailableDecodable<URL>?
}
但这需要我不断提及url.wrapped
.
有没有更好的解决方案?
如果你使用的是 Swift 5.1,你可以使用@propertyWrapper
:
let json = """
{
"name": "Fred",
"url": ""
}
""".data(using: .utf8)!
@propertyWrapper
struct FailableDecodable<Wrapped: Decodable>: Decodable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
}
struct Employee: Decodable {
let name: String
@FailableDecodable
private(set) var url: URL?
}
let employee = try! JSONDecoder().decode(Employee.self, from: json)
employee.url // nil
编辑 — 可编码版本
如果还需要Encodable
顶级结构,则可以使用Codable
属性包装器的一致性。
@propertyWrapper
struct FailableDecodable<Wrapped: Codable>: Codable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
如果url
nil
这将输出一个带有url: null
JSON
{"name":"Fred","url":null}
如果您希望在nil
时不输出url
属性,则需要在Employee
中实现自定义编码(使用encode(to:)
((这将减轻使用属性包装器的好处(。
注意:使用encode(to:)
的默认实现(不实现它(有效,但在nil
时输出一个空对象url
:
{"name":"Fred","url":{}}
如果您收到Codable
Return from initializer without initializing all stored properties
警告,则包含以下内容@FailableDecodable
结构初始值设定项将解决此问题。
@propertyWrapper
struct FailableDecodable<Wrapped: Codable>: Codable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}
除了上面出色的@propertyWrapper
解决方案之外,我还想在 JSON 中不存在url
键时修复失败。
它永远不会进入init(from decoder: Decoder)
方法,因为密钥不存在,因此我们需要在KeyedCodingContainer
上添加一个扩展来处理这种情况。
extension KeyedDecodingContainer {
func decode<Wrapped: Codable>(_ type: FailableDecodable<Wrapped>.Type, forKey key: K) throws -> FailableDecodable<Wrapped> {
if let value = try self.decodeIfPresent(type, forKey: key) {
return value
}
return FailableDecodable(wrappedValue: nil)
}
}
我还需要手动添加另一个初始值设定项来FailableDecodable
进行编译。
init(wrappedValue: Wrapped?) {
self.wrappedValue = wrappedValue
}
我发现通过这些更改,上面@rraphael的答案是完美的!