【问题标题】:Decoding invalid URLs as nil将无效 URL 解码为 nil
【发布时间】:2019-10-17 10:49:09
【问题描述】:

在你回答之前:

我知道:

  • 空字符串是无效的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

有没有更好的解决方案?

【问题讨论】:

  • 如果不是有效的 url,内置解码器会失败,您必须覆盖初始化解码器
  • 多久调用一次 URL?如果只有一次,那么您可以考虑将模型中的类型设置为字符串,并在 JSON 序列化发生后将其转换为 URL?。
  • @Sh_Khan,我知道,谢谢。我想要一个不需要我为每个包含可选 URL 的对象编写自定义解码器的解决方案。因此,为什么我试图覆盖Optional where Wrapped == URL,但没有成功,以及FailableDecodable(无论何时使用URL,都需要使用.wrapped

标签: swift url codable


【解决方案1】:

如果你使用的是 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: null 的JSON

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

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

注意:使用encode(to:)的默认实现(不实现)可以,但是当urlnil时输出一个空对象:

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

【讨论】:

  • 给那个人一支雪茄!这正是我一直在寻找的答案。我在init 方法中添加了nil 的预检查,如下所示……if container.decodeNil() { wrappedValue = nil return }。这可以防止try? 每次都出现快速错误。
  • 这很酷!如果你也需要 Encodable,你会如何攻击它?
  • @iCediCe 我用Codable 版本编辑了我的答案
【解决方案2】:

如果您的 Codable 结构初始化程序包含 @FailableDecodable 收到 Return from initializer without initializing all stored properties 警告,则下面将解决它。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-21
    相关资源
    最近更新 更多