Satyam
Satyam

Reputation: 15904

URL Encoding a string with special characters

Please consider below code that I implemented in my iOS App:

let kBaseURL = "https://www.sampledomain.com/"
let method = "api/MasterData/GetCompanyList?jsonArgs={\"AccountID\":%d}"

private func prepareURL(api: String) -> String {
    return (kBaseURL + api).addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
}

In above code, "method" has "%d" which will be replaced by account id (at run time) that is an integer.

let accountID = 20
let methodToCall = String(format: method, accountID)
var apiRequest = URLRequest(url: URL(string: prepareURL(api: methodToCall))!)

Above code is crashing as URL is nil which is due to "method" that has special characters in it.

Crash reason given by Xcode is: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Upvotes: 2

Views: 1376

Answers (2)

beshio
beshio

Reputation: 804

As found in Apple's doc, if you have some characters in the string which are illegal in a URL, URL(string:) returns nil. At this case, you need to % encode as follows.

let url = URL(string: "123\"456") // this returns nil due to double quote

So you need to mofify to

let url = URL(string: "123\"456".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)

I came across the same issue when I was handling non-English letters; for my case they are "Kanji". So, I always use percent encoding w/o checking what's in the string when calling URL(string:). Actually, there are so many illegal URL letters w/ "Kanji" and it's next to impossible to check what's legal and what's not manually. URL(string:) w/ the percent encoded string by addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) always works nicely for me.

Upvotes: 1

Matic Oblak
Matic Oblak

Reputation: 16794

You need to encode those characters as well. You can check what percent escaped values are for { and } and put them in manually. But I rather suggest you have the ability to create a query string from dictionary. Something like the following should fix your issue:

func createQueryString(from parameters: [String: String]) -> String {
    let components: [String] = parameters.compactMap { item in
        guard let encoded = item.value.addingPercentEncoding(withAllowedCharacters: CharacterSet(charactersIn: "!*'\"(){};:@&=+$,/?%#[]% ").inverted) else { return nil }
        return item.key + "=" + encoded
    }
    return components.joined(separator: "&")
}

Now all you need to do is use it and ensure jsonArgs={\"AccountID\":%d} is used in a dictionary as well. Try something like:

func generateAccountsRequest(accountID: Int) -> URLRequest {
    let kBaseURL = "https://www.sampledomain.com"
    let endpoint = "api/MasterData/GetCompanyList"

    func prepareURL(endpoint: String, query: [String: String]) -> String {
        return kBaseURL + "/" + endpoint + "?" + createQueryString(from: query)
    }

    return URLRequest(url: URL(string: prepareURL(endpoint: endpoint, query: ["jsonArgs": "{\"AccountID\":\(accountID)}"]))!)
}

Upvotes: 0

Related Questions