Skip to content

Instantly share code, notes, and snippets.

@jackreichert
Last active March 19, 2020 07:12
Show Gist options
  • Save jackreichert/81a7ce9d0cefd5d1780f to your computer and use it in GitHub Desktop.
Save jackreichert/81a7ce9d0cefd5d1780f to your computer and use it in GitHub Desktop.
This is a swift extension for NSURL so you can parse the query string and get back a dictionary of the variables.
extension NSURL {
func getKeyVals() -> Dictionary<String, String>? {
var results = [String:String]()
var keyValues = self.query?.componentsSeparatedByString("&")
if keyValues?.count > 0 {
for pair in keyValues! {
let kv = pair.componentsSeparatedByString("=")
if kv.count > 1 {
results.updateValue(kv[1], forKey: kv[0])
}
}
}
return results
}
}
@koenpunt
Copy link

I use it to parse the fragment of a NSURL.

extension NSURL {
    var fragments: [String: String] {
        var results = [String: String]()
        if let pairs = self.fragment?.componentsSeparatedByString("&") where pairs.count > 0 {
            for pair: String in pairs {
                if let keyValue = pair.componentsSeparatedByString("=") as [String]? {
                    results.updateValue(keyValue[1], forKey: keyValue[0])
                }
            }
        }
        return results
    }
}

// 
let url = NSURL(string: "http://www.google.com/search#q=someterm")
print(url.fragments)

@halldorel
Copy link

@koenput Beware, that if the fragment is empty, then keyValue is an array of a single empty string, and gets through as [String]?, gets splitted with pair.componentsSeparatedByString("=") resulting in a brutal crash when indexed blindly as having two elements keyValue[0] and keyValue[1]. Should probably add an extra nil check.

@halldorel
Copy link

Works beautifully this way (maybe not too pretty though):

// Returns a dict of a decoded query string, if present in the URL fragment.
extension NSURL {
    var fragments: [String: String] {
        var results = [String: String]()
        if let pairs = self.fragment?.componentsSeparatedByString("&") where pairs.count > 0 {
            for pair: String in pairs {
                if let keyValue = pair.componentsSeparatedByString("=") as [String]? {
                    if(keyValue.count > 1) {
                        results.updateValue(keyValue[1], forKey: keyValue[0])
                    }
                }
            }
        }
        return results
    }
} 

@nevstad
Copy link

nevstad commented Mar 7, 2016

This is really what NSURLComponents is for. If you still feel the need to have it in [String: String]you could do something like:

extension NSURL {
    var queryItems: [String: String]? {
        return NSURLComponents(URL: self, resolvingAgainstBaseURL: false)?
            .queryItems?
            .reduce([:], combine: { (var params: [String: String], item) -> [String: String] in
                params[item.name] = item.value
                return params
            })
    }
}

@jasonwiener
Copy link

@nevstad, that's pretty perfect. thanks kindly!

@pschneider
Copy link

@nevstad Thank you for your snippet.

As of Swift 2.2 var parameters are marked as deprecated. I've updated your snippet to fix the deprecation warning. There might be an easier way though.

extension NSURL {
    var queryItems: [String: String]? {
        var params = [String: String]()
        return NSURLComponents(URL: self, resolvingAgainstBaseURL: false)?
            .queryItems?
            .reduce([:], combine: { (_, item) -> [String: String] in
                params[item.name] = item.value
                return params
            })
    }
}

@mihai8804858
Copy link

extension URL {
    var queryItems: [String: String]? {
        return URLComponents(url: self, resolvingAgainstBaseURL: false)?
            .queryItems?
            .flatMap { $0.dictionaryRepresentation }
            .reduce([:], +)
    }
}

extension URLQueryItem {
    var dictionaryRepresentation: [String: String]? {
        if let value = value {
            return [name: value]
        }
        return nil
    }
}

Works with Swift 3. ;)

@nataliachodelski
Copy link

nataliachodelski commented Dec 1, 2016

I tried the above solution by @mihai8804858 with Swift 3 / Xcode 8 but I couldn't get it to work. The "+" in the .reduce line of the URL extension produced a compile error: "ambiguous reference to member +". Mihai, I would still love to try out your solution - any chance you can clarify what is going on in thereduce line?

I was able to get @nevstad's NSURL extension working with Swift 3 so wanted to share that. It worked perfectly for my purpose (parsing a universal deep link URL).

extension NSURL {
    var queryItems: [String: String]? {
        var params = [String: String]()
        return NSURLComponents(url: self as URL, resolvingAgainstBaseURL: false)?
            .queryItems?
            .reduce([:], { (_, item) -> [String: String] in
                params[item.name] = item.value
                return params
            })
    }
 }

Used like this (it returned a [String : String] array):

 let url = NSURL(string: myURLString)
 if let queryString = url?.queryItems {
     print(queryString)
     for (index, item) in queryString.enumerated() {
         print(item.key)
         print(item.value)
     }
 }

@sudhyk
Copy link

sudhyk commented Dec 22, 2016

extension URL {
    var queryItems: [String: String]? {
        return URLComponents(url: self, resolvingAgainstBaseURL: false)?
            .queryItems?
            .flatMap { $0.dictionaryRepresentation }
            .reduce([:], +)
    }
}

extension URLQueryItem {
    var dictionaryRepresentation: [String: String]? {
        if let value = value {
            return [name: value]
        }
        return nil
    }
}

func +<Key, Value> (lhs: [Key: Value], rhs: [Key: Value]) -> [Key: Value] {
    var result = lhs
    rhs.forEach{ result[$0] = $1 }
    return result
}

This works great for Swift 3

@scalessec
Copy link

Cleaned up Swift 3 version without operator overloading:

extension URL {
    public var queryItems: [String: String] {
        var params = [String: String]()
        return URLComponents(url: self, resolvingAgainstBaseURL: false)?
            .queryItems?
            .reduce([:], { (_, item) -> [String: String] in
                params[item.name] = item.value
                return params
            }) ?? [:]
    }  
}

@ivnsch
Copy link

ivnsch commented Aug 21, 2017

I don't like that much using reduce for side effects, this also works and is more readable IMO:

var params = [String: String]()
queryItems?.forEach { item in
    params[item.name] = item.value
}
return params

I also would put, for consistency, this in an extension of URLComponents (instead of directly in URL), which contains accessors for all the other parts of the URL.

@tncowart
Copy link

tncowart commented Jan 18, 2019

@i-schuetz your solution can lose information. a perfectly cromulent query might look like ?name=Alice&name=Bob&name=Ted&name=Carol. If you ran your fragment on that queryItem list params["name"] would only return Carol

Here's an extension to URL that works with Swift 4.2

extension URL {
    private func splitQuery(_ query: String) -> [String: [String]] {
        return query.components(separatedBy: "&").map { $0.components(separatedBy: "=") }.reduce(into: [String: [String]]()) { result, element in
            guard !element.isEmpty,
                let key = element[0].removingPercentEncoding,
                let value = element.count >= 2 ? element[1].removingPercentEncoding : "" else { return }
            var values = result[key, default: [String]()]
            values.append(value)
            result[key] = values
        }
    }

    var fragmentItems: [String: [String]] {
        guard let fragment = self.fragment else {
            return [:]
        }

        return splitQuery(fragment)
    }

    var queryItems: [String: [String]] {
        guard let query = self.query else {
            return [:]
        }

        return splitQuery(query)
    }
}
// queryItems has the same behavior as fragmentItems

let test = URL(string: "http://example.com#name=Alice%20Barker&name=Bob&job=developer")!
test.fragmentItems // ["name": ["Alice Barker", "Bob"], "job": ["developer"]]
test.fragmentItems["name"] // Optional(["Alice Barker", "Bob"])

let test2 = URL(string: "http://example.com#name")!
test2.fragmentItems // ["name": [""]]
test2.fragmentItems["name"] // Optional([""])

let test3 = URL(string: "http://example.com#")!
test3.fragmentItems // ["": [""]]
test3.fragmentItems["name"] // nil

let test4 = URL(string: "http://example.com")!
test4.fragmentItems // [:]
test4.fragmentItems["name"] // nil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment