将无效网址解码为 nil



在回答之前:

我知道:

  • 空字符串是无效URL
  • 我可以为Employee编写一个自定义解码器
  • 我可以宣布urlString

我正在寻找的是解码可选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)
}
}

如果urlnil这将输出一个带有url: nullJSON

{"name":"Fred","url":null}

如果您希望在nil时不输出url属性,则需要在Employee中实现自定义编码(使用encode(to:)((这将减轻使用属性包装器的好处(。

注意:使用encode(to:)的默认实现(不实现它(有效,但在nil时输出一个空对象url

{"name":"Fred","url":{}}

如果您收到CodableReturn 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的答案是完美的!

相关内容

  • 没有找到相关文章

最新更新