从可解码模型解码 url 其中包含 space

Decode url with space in it from decodable model

我有以下结构并且响应解析失败,因为 url (https://example.url.com/test a) 中有一个 space。如何在反序列化层中使用 %20 值对其进行转义并将其保持为 URL 类型?

struct Response: Decodable {
    let url: URL

    enum CodingKeys: String, CodingKey {
        case url
    }

    init(from decoder: Decoder) throws {
        self.url = try decoder.container(keyedBy: CodingKeys.self).decode(URL.self, forKey: .url)
    }
}

let string = "{\"url\":\"https://example.url.com/test a\"}"
let responseModel = try? JSONDecoder().decode(Response.self, from: string.data(using: .utf8)!)
print(responseModel?.url)

我认为使用 JSONDecoder 自定义或序列化层是不可能的,正如您在 post 中提到的那样。你能做到的最好的就是这样做:

struct Response: Decodable {
    let urlString: String

    var url: URL {
        URL(string: urlString.replacingOccurrences(of: " ", with: "%20"))!
    }

    enum CodingKeys: String, CodingKey {
        case urlString = "url"
    }
}

注意:如果您没有自定义实现,则不需要 init(decoder:)。如果 属性 名称与字符串键相同(在您的情况下 url 键是多余的),也不需要 CodingKeys 枚举。

您不能将 url 解码为 URL,如 decode(URL.self...),因为它 不是 URL .将你的 url 属性 更改为 String,将此值解码为 String.self,并处理将 String 转换为 URL 之后的 过程。

正如 Rob 已经提到的,最好在后端解决问题而不是修复解码部分。如果你不能在服务器端修复它,你可以将你的 url 解码为一个字符串,百分比转义它然后使用结果字符串来初始化你的 url 属性:

struct Response: Codable {
    let url: URL
}

extension Response {
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let string = try container.decode(String.self, forKey: .url)
        guard
            let percentEncoded = string
                .addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
            let url = URL(string: percentEncoded)
        else {
            throw DecodingError.dataCorruptedError(forKey: .url,
                                                   in: container,
                debugDescription: "Invalid url string: \(string)")
        }
        self.url = url
    }
}

游乐场测试

let jsonString = #"{"url":"https://example.url.com/test a"}"#
do {
    let responseModel = try JSONDecoder().decode(Response.self, from: Data(jsonString.utf8))
    print(responseModel.url)
} catch { print(error) }

这将打印

https://example.url.com/test%20a