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