问题
An API I work with provides URL links, that can contain special characters like "http://es.dbpedia.org/resource/Análisis_de_datos" (the letter "á" inside).
It's an absolutely valid URL, however, if a decodable class contains an optional URL?
variable, it can't be decoded.
I can change URL?
to String?
in my class and have a computed variable with something like URL(string: urlString.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)
but perhaps there is a more elegant solution.
To reproduce in Playground:
struct Container: Encodable {
let url: String
}
struct Response: Decodable {
let url: URL?
}
let container = Container(url: "http://es.dbpedia.org/resource/Análisis_de_datos")
let encoder = JSONEncoder()
let encodedData = try encoder.encode(container)
let decoder = JSONDecoder()
let response = try? decoder.decode(Response.self, from: encodedData)
// response == nil, as it can't be decoded.
let url = response?.url
回答1:
There are multiple way to overcome this, but I think using a property wrapper is probably the most elegant:
@propertyWrapper
struct URLPercentEncoding {
var wrappedValue: URL
}
extension URLPercentEncoding: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let str = try? container.decode(String.self),
let encoded = str.addingPercentEncoding(
withAllowedCharacters: .urlFragmentAllowed),
let url = URL(string: encoded) {
self.wrappedValue = url
} else {
throw DecodingError.dataCorrupted(
.init(codingPath: container.codingPath, debugDescription: "Corrupted url"))
}
}
}
Then you could use it like so without the consumer of this model having to know anything about it:
struct Response: Decodable {
@URLPercentEncoding let url: URL
}
回答2:
You can extend KeyedDecodingContainer and implement your own URL decoding method:
extension KeyedDecodingContainer {
func decode(_ type: URL.Type, forKey key: K) throws -> URL {
let string = try decode(String.self, forKey: key)
guard let url = URL(string: string.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "")
else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "The stringvalue for the key \(key) couldn't be converted into a URL value: \(string)"))
}
return url
}
// decoding an optional URL
func decodeIfPresent(_ type: URL.Type, forKey key: K) throws -> URL? {
try URL(string: decode(String.self, forKey: key).addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "")
}
}
struct Container: Encodable {
let url: String
}
struct Response: Decodable {
let url: URL
}
let container = Container(url: "http://es.dbpedia.org/resource/Análisis_de_datos")
do {
let encodedData = try encoder.encode(container)
print(String(data: encodedData, encoding: .utf8))
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: encodedData)
print(response)
} catch {
print(error)
}
This will print:
Optional("{"url":"http:\/\/es.dbpedia.org\/resource\/Análisis_de_datos"}")
Response(url: http://es.dbpedia.org/resource/An%C3%A1lisis_de_datos)
来源:https://stackoverflow.com/questions/63803225/decode-urls-with-special-characters-in-swift