Decode URLs with special characters in Swift

社会主义新天地 提交于 2021-01-27 19:08:18

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!