Parse NSURL query property

| | August 7, 2015

I have a URL like myApp://action/1?parameter=2&secondparameter=3

With the property query I get following part of my URL

parameter=2&secondparameter=3

Is there any way easy to put this in a NSDictionary or an Array?

Thx a lot

13 Responses to “Parse NSURL query property”

  1. Erik Tjernlund on November 30, -0001 @ 12:00 AM

    Swift 1.2

    One (verbose) line Swift—using map and reduce—to create a dictionary of a URL query string:

    split("p1=v1&p2=v2") { (qSeparator) in
        qSeparator == "&"
    }.map { (queries) in
        split(queries) { (valueSeparator) in
            valueSeparator == "="
        }
    }.reduce([:]) { (var dict: [String:String], p) in
        dict[p[0]] = p[1]
        return dict
    }
    
    // ["p1": "v1", "p2": "v2"]
    

    Used as an extension on NSURL:

    extension NSURL {
    
        /**
         * URL query string as dictionary. Empty dictionary if query string is nil.
         */
        public var queryValues : [String:String] {
            get {
                if let q = self.query {
                    return split(q) { (qSeparator) in
                            qSeparator == "&"
                        }.map { (queries) in
                            split(queries) { (valueSeparator) in valueSeparator == "=" }
                        }.reduce([:]) { (var dict: [String:String], p) in
                            dict[p[0]] = p[1]
                            return dict
                    }
                } else {
                    return [:]
                }
            }
        }
    
    }
    

    Example:

    let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
    let queryDict = url.queryValues
    
    // ["p1": "v1", "p2": "v2"]
    

    Please note, if using OS X 10.10 or iOS 8 (or later), it’s probably better to use NSURLComponents and the queryItems property and create the dictionary from the NSURLQueryItems directly.

    Swift 2

    In Swift 2, changes in String, breaks the above solution and even if a one-liner works, it’s too messy for my taste.

    Using Swift 2, this works:

    let pairList = split("p1=v1&p2=v2".characters) {
        $0 == "&"
    }.map {
        String($0)
    }.map { (q) in
        split(q.characters) {
            $0 == "="
        }.map {
            String($0)
        }
    }
    
    // pairList now is [["p1","v1"],["p2","v2"]]
    
    pairList.reduce([String:String]()) { (var dict:[String:String], pair) in
        dict[String(pair[0])] = String(pair[1])
        return dict
    }
    
    // ["p1": "v1", "p2": "v2"]
    

    Too bad that with the two extra map calls needed, the readability (and general point) of this solution is diminished.

    A footnote to the NSURL extension is that it’s actually possible in Swift to give the property the same name as the existing string property—query. I didn’t know until I tried it, but the polymorphism in Swift lets you differ only on the return type. So if the extended NSURL property is public var query: [String:String] it works. I didn’t use this in the example as I find it a little bit crazy, but it does work …

  2. Kristian Kraljic on November 30, -0001 @ 12:00 AM

    All previous posts do not do the url encoding properly. I would suggest the following methods:

    +(NSString*)concatenateQuery:(NSDictionary*)parameters {
        if([parameters count]==0) return nil;
        NSMutableString* query = [NSMutableString string];
        for(NSString* parameter in [parameters allKeys])
            [query appendFormat:@"&%@=%@",[parameter stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet],[[parameters objectForKey:parameter] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];
        return [[query substringFromIndex:1] copy];
    }
    +(NSDictionary*)splitQuery:(NSString*)query {
        if([query length]==0) return nil;
        NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
        for(NSString* parameter in [query componentsSeparatedByString:@"&"]) {
            NSRange range = [parameter rangeOfString:@"="];
            if(range.location!=NSNotFound)
                [parameters setObject:[[parameter substringFromIndex:range.location+range.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:[[parameter substringToIndex:range.location] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
            else [parameters setObject:[[NSString alloc] init] forKey:[parameter stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        }
        return [parameters copy];
    }
    
  3. In iOS 8 and OSX 10.10 you can use queryItems in NSURLComponents.

    When you get this property’s value, the NSURLComponents class parses the query string and returns an array of NSURLQueryItem objects, each of which represents a single key-value pair, in the order in which they appear in the original query string.

    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url 
                                                resolvingAgainstBaseURL:NO];
    NSArray *queryItems = urlComponents.queryItems;
    NSString *param1 = [self valueForKey:@"param1" 
                      fromQueryItems:queryItems];
    
    …
    
    - (NSString *)valueForKey:(NSString *)key
               fromQueryItems:(NSArray *)queryItems
    {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", key];
        NSURLQueryItem *queryItem = [[queryItems 
                                      filteredArrayUsingPredicate:predicate]
                                     firstObject];
        return queryItem.value;
    }
    
  4. Hendrik wrote a nice example for extension in this question, however I had to re-write it to not use any objective-c library methods. Using NSArray in swift is not the correct approach.

    This is the result, all swift and a bit more safe. The usage example will be less lines of code with Swift 1.2.

    public extension NSURL {
        /*
        Set an array with all the query items
        */
        var allQueryItems: [NSURLQueryItem] {
            get {
                let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
                if let allQueryItems = components.queryItems {
                    return allQueryItems as [NSURLQueryItem]
                } else {
                    return []
                }
            }
        }
    
        /**
        Get a query item form the URL query
    
        :param: key The parameter to fetch from the URL query
    
        :returns: `NSURLQueryItem` the query item
        */
        public func queryItemForKey(key: String) -> NSURLQueryItem? {
            let filteredArray = filter(allQueryItems) { $0.name == key }
    
            if filteredArray.count > 0 {
                return filteredArray.first
            } else {
                return nil
            }
        }
    }
    

    Usage:

    let queryItem = url.queryItemForKey("myItem")
    

    Or, more detailed usage:

    if let url = NSURL(string: "http://www.domain.com/?myItem=something") {
        if let queryItem = url.queryItemForKey("myItem") {
            if let value = queryItem.value {
                println("The value of 'myItem' is: (value)")
            }
        }
    }
    
  5. According to the already very clean answer of Onato I wrote an extension for NSURL in Swift where you can get a query param like this:

    e.g. the URL contains the pair param=some_value

    let queryItem = url.queryItemForKey("param")
    let value = queryItem.value // would get String "someValue"
    

    The extension looks like:

    extension NSURL {
    
      var allQueryItems: [NSURLQueryItem] {
          get {
              let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
              let allQueryItems = components.queryItems!
              return allQueryItems as [NSURLQueryItem]
          }
      }
    
      func queryItemForKey(key: String) -> NSURLQueryItem? {
    
          let predicate = NSPredicate(format: "name=%@", key)!
          return (allQueryItems as NSArray).filteredArrayUsingPredicate(predicate).first as? NSURLQueryItem
    
      }
    }
    
  6. For those using Bolts Framework you can use:

    NSDictionary *parameters = [BFURL URLWithURL:yourURL].inputQueryParameters;
    

    Remember to import:

    #import <Bolts/BFURL.h>
    

    If you happen to have Facebook SDK in your project, you also have Bolts. Facebook is using this framework as a dependency.

  7. Here is the extension in swift:

    extension NSURL{
            func queryParams() -> [String:AnyObject] {
                var info : [String:AnyObject] = [String:AnyObject]()
                if let queryString = self.query{
                    for parameter in queryString.componentsSeparatedByString("&"){
                        let parts = parameter.componentsSeparatedByString("=")
                        if parts.count > 1{
                            let key = (parts[0] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
                            let value = (parts[1] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
                            if key != nil && value != nil{
                                info[key!] = value
                            }
                        }
                    }
                }
                return info
            }
        }
    
  8. Amin Negm-Awad on November 30, -0001 @ 12:00 AM

    I published a simple class doing the job under MIT:

    https://github.com/anegmawad/URLQueryToCocoa

    With it you can have arrays and objects in the query, which are collected and glued together

    For Example

    users[0][firstName]=Amin&users[0][lastName]=Negm&name=Devs&users[1][lastName]=Kienle&users[1][firstName]=Christian
    

    will become:

    @{
       name : @"Devs",
       users :
       @[
          @{
             firstName = @"Amin",
             lastName = @"Negm"
          },
          @{
             firstName = @"Christian",
             lastName = @"Kienle"
          }
       ]
     }
    

    You can think of it as a URL query counterpart of NSJSONSerializer.

  9. This class is a nice solution for url parsing.

    .h file

    @interface URLParser : NSObject {
        NSArray *variables;
    }
    
    @property (nonatomic, retain) NSArray *variables;
    
    - (id)initWithURLString:(NSString *)url;
    - (NSString *)valueForVariable:(NSString *)varName;
    
    @end
    

    .m file

    #import "URLParser.h"
    
    @implementation URLParser
    @synthesize variables;
    
    - (id) initWithURLString:(NSString *)url{
        self = [super init];
        if (self != nil) {
            NSString *string = url;
            NSScanner *scanner = [NSScanner scannerWithString:string];
            [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
            NSString *tempString;
            NSMutableArray *vars = [NSMutableArray new];
            [scanner scanUpToString:@"?" intoString:nil];       //ignore the beginning of the string and skip to the vars
            while ([scanner scanUpToString:@"&" intoString:&tempString]) {
                [vars addObject:[tempString copy]];
            }
            self.variables = vars;
        }
        return self;
    }
    
    - (NSString *)valueForVariable:(NSString *)varName {
        for (NSString *var in self.variables) {
            if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:@"="]]) {
                NSString *varValue = [var substringFromIndex:[varName length]+1];
                return varValue;
            }
        }
        return nil;
    }
    
    @end
    
  10. Try this ;)!

    NSString *query = @"parameter=2&secondparameter=3"; // replace this with [url query];
    NSArray *components = [query componentsSeparatedByString:@"&"];
    NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
    for (NSString *component in components) {
        NSArray *subcomponents = [component componentsSeparatedByString:@"="];
        [parameters setObject:[[subcomponents objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
                       forKey:[[subcomponents objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    }
    
  11. I had reason to write some extensions for this behavior that might come in handy. First the header:

    #import <Foundation/Foundation.h>
    
    @interface NSString (XQueryComponents)
    - (NSString *)stringByDecodingURLFormat;
    - (NSString *)stringByEncodingURLFormat;
    - (NSMutableDictionary *)dictionaryFromQueryComponents;
    @end
    
    @interface NSURL (XQueryComponents)
    - (NSMutableDictionary *)queryComponents;
    @end
    
    @interface NSDictionary (XQueryComponents)
    - (NSString *)stringFromQueryComponents;
    @end
    

    These methods extend NSString, NSURL, and NSDictionary, to allow you to convert to and from query components strings and dictionary objects containing the results.

    Now the related .m code:

    #import "XQueryComponents.h"
    
    @implementation NSString (XQueryComponents)
    - (NSString *)stringByDecodingURLFormat
    {
        NSString *result = [self stringByReplacingOccurrencesOfString:@"+" withString:@" "];
        result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        return result;
    }
    
    - (NSString *)stringByEncodingURLFormat
    {
        NSString *result = [self stringByReplacingOccurrencesOfString:@" " withString:@"+"];
        result = [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        return result;
    }
    
    - (NSMutableDictionary *)dictionaryFromQueryComponents
    {
        NSMutableDictionary *queryComponents = [NSMutableDictionary dictionary];
        for(NSString *keyValuePairString in [self componentsSeparatedByString:@"&"])
        {
            NSArray *keyValuePairArray = [keyValuePairString componentsSeparatedByString:@"="];
            if ([keyValuePairArray count] < 2) continue; // Verify that there is at least one key, and at least one value.  Ignore extra = signs
            NSString *key = [[keyValuePairArray objectAtIndex:0] stringByDecodingURLFormat];
            NSString *value = [[keyValuePairArray objectAtIndex:1] stringByDecodingURLFormat];
            NSMutableArray *results = [queryComponents objectForKey:key]; // URL spec says that multiple values are allowed per key
            if(!results) // First object
            {
                results = [NSMutableArray arrayWithCapacity:1];
                [queryComponents setObject:results forKey:key];
            }
            [results addObject:value];
        }
        return queryComponents;
    }
    @end
    
    @implementation NSURL (XQueryComponents)
    - (NSMutableDictionary *)queryComponents
    {
        return [[self query] dictionaryFromQueryComponents];
    }
    @end
    
    @implementation NSDictionary (XQueryComponents)
    - (NSString *)stringFromQueryComponents
    {
        NSString *result = nil;
        for(__strong NSString *key in [self allKeys])
        {
            key = [key stringByEncodingURLFormat];
            NSArray *allValues = [self objectForKey:key];
            if([allValues isKindOfClass:[NSArray class]])
                for(__strong NSString *value in allValues)
                {
                    value = [[value description] stringByEncodingURLFormat];
                    if(!result)
                        result = [NSString stringWithFormat:@"%@=%@",key,value];
                    else 
                        result = [result stringByAppendingFormat:@"&%@=%@",key,value];
                }
            else {
                NSString *value = [[allValues description] stringByEncodingURLFormat];
                if(!result)
                    result = [NSString stringWithFormat:@"%@=%@",key,value];
                else 
                    result = [result stringByAppendingFormat:@"&%@=%@",key,value];
            }
        }
        return result;
    }
    @end
    
  12. Most robust solution if you are using a URL to pass data from the web app to the phone and you want to pass arrays, numbers, strings, …

    JSON encode your object in PHP

    header("Location: myAppAction://".urlencode(json_encode($YOUROBJECT)));
    

    And JSON decode the result in iOS

    NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    
  13. Something like that:

    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    for (NSString *param in [url componentsSeparatedByString:@"&"]) {
      NSArray *elts = [param componentsSeparatedByString:@"="];
      if([elts count] < 2) continue;
      [params setObject:[elts objectAtIndex:1] forKey:[elts objectAtIndex:0]];
    }
    

    Note : This is sample code. All error cases are not managed.

Leave a Reply