swift 将无效URL解码为nil

qltillow  于 2023-05-05  发布在  Swift
关注(0)|答案(3)|浏览(151)
  • 在你回答之前 *

我知道

  • 空字符串是无效的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
有更好的解决方案吗?

9fkzdhlc

9fkzdhlc1#

如果你使用的是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:))(这将减少使用属性 Package 器的好处)。

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

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

1wnzp6jl2#

如果你得到Return from initializer without initializing all stored properties警告,你的Codable结构初始化器包含下面的@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
    }
    
}
xqkwcwgp

xqkwcwgp3#

除了上面优秀的@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的答案是完美的!

相关问题