Using Swift-4.1, Xcode-9.3.1, iOS-11.3.1
我使用Codable协议来解码JSON文件。一切正常,除了直到我在 URL 中有一个国际化域名(在本例中,带有德语变音"ä
")的那一刻(例如:http://www.rhätische-zeitung.ch)。
这会导致以下代码中的解码器错误:
func loadJSON(url: URL) -> Media? {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let media = try decoder.decode(Media.self, from: data)
return media
} catch {
print("error:(error)")
}
return nil
}
错误消息是:
The Codable protocol does not seem to be able to decode this URL form
my JSON-file into the needed Struct.
这是结构:
struct Media: Codable {
var publisher: [MediaPublisher]
}
struct MediaPublisher: Codable {
var title: String?
var homepage_url: URL?
}
以下是 JSON 摘录:
{
"publisher": [
{
"title" : "Rhätische-Zeitung",
"homepage_url" : "http://www.rhätische-zeitung.ch",
}
]
}
由于 JSON 文件来自外部,因此我无法控制内容。因此,替换 JSON 中的 URL 不是一种选择! (因此,我无法将 JSON 中的 URL 替换为接受的国际化形式,例如:www.xn--rhtische-zeitung-wnb.ch
) !!
我知道有一些技术可以将自定义初始化器放入结构定义中(请参阅下面的试用版......) - 但由于是 Codable 的新手,我不知道如何针对当前的 URL-Umlaut 问题做到这一点。我在下面放置的自定义初始化器确实为有问题的 URL 返回 nil。我需要改变什么?
或者有没有另一种方法可以让带有变音符号的 URL 的 JSON 解码工作?
这是结构体,这次使用自定义初始化器:
(至少有了这个,我可以摆脱上面的错误消息......但是现在 URL 似乎为零,这也不是我想要的)
struct Media: Codable {
var publisher: [MediaPublisher]
}
struct MediaPublisher: Codable {
var title: String?
var homepage_url: URL?
// default initializer
init(title: String?, homepage_url: URL?) {
self.title = title
self.homepage_url = homepage_url
}
// custom initializer
init(from decoder: Decoder) throws {
let map = try decoder.container(keyedBy: CodingKeys.self)
self.title = try? map.decode(String.self, forKey: .title)
self.homepage_url = try? map.decode(URL.self, forKey: .homepage_url)
}
private enum CodingKeys: CodingKey {
case title
case homepage_url
}
}
我只是偶然发现了同样的问题。该解决方案实际上相当简单,在我的简短测试中运行良好。
这个想法是不让解码器将值解码为 URL,因为它希望它后面的字符串采用某种格式。我们可以做的是直接将值解码为字符串并将其手动转换为 URL。
我写了一个为我完成这项工作的小扩展:
extension KeyedDecodingContainer {
/// Decodes the string at the given key as a URL. This allows for special characters like umlauts to be decoded correctly.
func decodeSanitizedURL(forKey key: KeyedDecodingContainer<K>.Key) throws -> URL {
let urlString = try self.decode(String.self, forKey: key)
// Sanitize string and attempt to convert it into a valid url
if let urlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let url = URL(string: urlString) {
return url
}
// Throw an error as the URL could not be decoded
else {
throw DecodingError.dataCorruptedError(forKey: key, in: self, debugDescription: "Could not decode (urlString)")
}
}
}
这允许简化init
方法的使用。
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.url = try values.decodeSanitizedURL(forKey: .url)
}
希望有所帮助,即使问题有点旧。