首页 > 解决方案 > 将无效 URL 解码为 nil

问题描述

在你回答之前:

我意识到:

我正在寻找的是解码可选URL本身的更好解决方案。我希望Codable我缺少一些魔法!


所以,我有 JSON 之类的

let json = Data("""
                {
                    "name": "Fred",
                    "url": ""
                }
                """.utf8)

以及包含可选 URL 的相应对象……</p>

struct Employee: Decodable {
    let name: String
    let url: URL?
}

由于urlJSON 无效,我希望它解码为nil,而不是抛出错误。

尝试以下不起作用(它不会被调用)......</p>

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

过去我用过……</p>

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

有更好的解决方案吗?

标签: swifturlcodable

解决方案


如果您使用的是 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这将输出一个 JSONurl: null

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

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

注意:使用(未实现)的默认实现有效,但在isencode(to:)时输出一个空对象:urlnil

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

推荐阅读