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