Creating URL query parameters from NSDictionary objects in ObjectiveC

With all the URL-handling objects lying around in the standard Cocoa libraries (NSURL, NSMutableURL, NSMutableURLRequest, etc), I know I must be overlooking an easy way to programmatically compose a GET request.

Currently I’m manually appending “?” followed by name value pairs joined by “&”, but all of my name and value pairs need to be manually encoded so NSMutableURLRequest doesn’t fail entirely when it tries to connect to the URL.

  • Storyboard Illegal Configuration iOS 7
  • Expression in FOR command (for (int i=0; i < (-1);i++){})
  • AFNetworking - How to make POST request
  • How do you create a wiggle animation similar to iphone deletion animation
  • How do I extend a NSManagedObject to contain MKAnnotation in Swift?
  • How to use Instrument in iOS
  • This feels like something I should be able to use a pre-baked API for…. is there anything out of the box to append an NSDictionary of query parameters to an NSURL? Is there another way I should approach this?

    12 Solutions Collect From Internet About “Creating URL query parameters from NSDictionary objects in ObjectiveC”

    Introduced in iOS8 and OS X 10.10 is NSURLQueryItem, which can be used to build queries. From the docs on NSURLQueryItem:

    An NSURLQueryItem object represents a single name/value pair for an item in the query portion of a URL. You use query items with the queryItems property of an NSURLComponents object.

    To create one use the designated initializer queryItemWithName:value: and then add them to NSURLComponents to generate an NSURL. For example:

    NSURLComponents *components = [NSURLComponents componentsWithString:@"http://stackoverflow.com"];
    NSURLQueryItem *search = [NSURLQueryItem queryItemWithName:@"q" value:@"ios"];
    NSURLQueryItem *count = [NSURLQueryItem queryItemWithName:@"count" value:@"10"];
    components.queryItems = @[ search, count ];
    NSURL *url = components.URL; // http://stackoverflow.com?q=ios&count=10
    

    Notice that the question mark and ampersand are automatically handled. Creating an NSURL from a dictionary of parameters is as simple as:

    NSDictionary *queryDictionary = @{ @"q": @"ios", @"count": @"10" };
    NSMutableArray *queryItems = [NSMutableArray array];
    for (NSString *key in queryDictionary) {
        [queryItems addObject:[NSURLQueryItem queryItemWithName:key value:queryDictionary[key]]];
    }
    components.queryItems = queryItems;
    

    I’ve also written a blog post on how to build URLs with NSURLComponents and NSURLQueryItems.

    You can create a category for NSDictionary to do this — there isn’t a standard way in the Cocoa library that I could find either. The code that I use looks like this:

    // file "NSDictionary+UrlEncoding.h"
    #import <cocoa/cocoa.h>
    
    @interface NSDictionary (UrlEncoding)
    
    -(NSString*) urlEncodedString;
    
    @end
    

    with this implementation:

    // file "NSDictionary+UrlEncoding.m"
    #import "NSDictionary+UrlEncoding.h"
    
    // helper function: get the string form of any object
    static NSString *toString(id object) {
      return [NSString stringWithFormat: @"%@", object];
    }
    
    // helper function: get the url encoded string form of any object
    static NSString *urlEncode(id object) {
      NSString *string = toString(object);
      return [string stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
    }
    
    @implementation NSDictionary (UrlEncoding)
    
    -(NSString*) urlEncodedString {
      NSMutableArray *parts = [NSMutableArray array];
      for (id key in self) {
        id value = [self objectForKey: key];
        NSString *part = [NSString stringWithFormat: @"%@=%@", urlEncode(key), urlEncode(value)];
        [parts addObject: part];
      }
      return [parts componentsJoinedByString: @"&"];
    }
    
    @end
    

    I think the code’s pretty straightforward, but I discuss it in some more detail at http://blog.ablepear.com/2008/12/urlencoding-category-for-nsdictionary.html.

    I wanted to use Chris’s answer, but it wasn’t written for Automatic Reference Counting (ARC) so I updated it. I thought I’d paste my solution in case anyone else has this same issue. Note: replace self with the instance or class name where appropriate.

    +(NSString*)urlEscapeString:(NSString *)unencodedString 
    {
        CFStringRef originalStringRef = (__bridge_retained CFStringRef)unencodedString;
        NSString *s = (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,originalStringRef, NULL, (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ", kCFStringEncodingUTF8);
        CFRelease(originalStringRef);
        return s;
    }
    
    
    +(NSString*)addQueryStringToUrlString:(NSString *)urlString withDictionary:(NSDictionary *)dictionary
    {
        NSMutableString *urlWithQuerystring = [[NSMutableString alloc] initWithString:urlString];
    
        for (id key in dictionary) {
            NSString *keyString = [key description];
            NSString *valueString = [[dictionary objectForKey:key] description];
    
            if ([urlWithQuerystring rangeOfString:@"?"].location == NSNotFound) {
                [urlWithQuerystring appendFormat:@"?%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
            } else {
                [urlWithQuerystring appendFormat:@"&%@=%@", [self urlEscapeString:keyString], [self urlEscapeString:valueString]];
            }
        }
        return urlWithQuerystring;
    }
    

    The other answers work great if the values are strings, however if the values are dictionaries or arrays then this code will handle that.

    Its important to note that there is no standard way of passing an array/dictionary via the query string but PHP handles this output just fine

    -(NSString *)serializeParams:(NSDictionary *)params {
        /*
    
         Convert an NSDictionary to a query string
    
         */
    
        NSMutableArray* pairs = [NSMutableArray array];
        for (NSString* key in [params keyEnumerator]) {
            id value = [params objectForKey:key];
            if ([value isKindOfClass:[NSDictionary class]]) {
                for (NSString *subKey in value) {
                    NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                                  (CFStringRef)[value objectForKey:subKey],
                                                                                                  NULL,
                                                                                                  (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                                  kCFStringEncodingUTF8);
                    [pairs addObject:[NSString stringWithFormat:@"%@[%@]=%@", key, subKey, escaped_value]];
                }
            } else if ([value isKindOfClass:[NSArray class]]) {
                for (NSString *subValue in value) {
                    NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                                  (CFStringRef)subValue,
                                                                                                  NULL,
                                                                                                  (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                                  kCFStringEncodingUTF8);
                    [pairs addObject:[NSString stringWithFormat:@"%@[]=%@", key, escaped_value]];
                }
            } else {
                NSString* escaped_value = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                              (CFStringRef)[params objectForKey:key],
                                                                                              NULL,
                                                                                              (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                              kCFStringEncodingUTF8);
                [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, escaped_value]];
                [escaped_value release];
            }
        }
        return [pairs componentsJoinedByString:@"&"];
    }
    

    Examples

    [foo] => bar
    [translations] => 
            {
                [one] => uno
                [two] => dos
                [three] => tres
            }
    

    foo=bar&translations[one]=uno&translations[two]=dos&translations[three]=tres

    [foo] => bar
    [translations] => 
            {
                uno
                dos
                tres
            }
    

    foo=bar&translations[]=uno&translations[]=dos&translations[]=tres

    I refactored and converted to ARC answer by AlBeebe

    - (NSString *)serializeParams:(NSDictionary *)params {
        NSMutableArray *pairs = NSMutableArray.array;
        for (NSString *key in params.keyEnumerator) {
            id value = params[key];
            if ([value isKindOfClass:[NSDictionary class]])
                for (NSString *subKey in value)
                    [pairs addObject:[NSString stringWithFormat:@"%@[%@]=%@", key, subKey, [self escapeValueForURLParameter:[value objectForKey:subKey]]]];
    
            else if ([value isKindOfClass:[NSArray class]])
                for (NSString *subValue in value)
                [pairs addObject:[NSString stringWithFormat:@"%@[]=%@", key, [self escapeValueForURLParameter:subValue]]];
    
            else
                [pairs addObject:[NSString stringWithFormat:@"%@=%@", key, [self escapeValueForURLParameter:value]]];
    
    }
    return [pairs componentsJoinedByString:@"&"];
    

    }

    - (NSString *)escapeValueForURLParameter:(NSString *)valueToEscape {
         return (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) valueToEscape,
                 NULL, (CFStringRef) @"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
    }
    

    If you are already using AFNetworking (as was the case with me), you can use it’s class AFHTTPRequestSerializer to create the required NSURLRequest.

    [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:@"YOUR_URL" parameters:@{PARAMS} error:nil];

    In case you only require the URL for your work, use NSURLRequest.URL.

    Here is a simple example in Swift (iOS8+):

    private let kSNStockInfoFetchRequestPath: String = "http://dev.markitondemand.com/Api/v2/Quote/json"
    
    private func SNStockInfoFetchRequestURL(symbol:String) -> NSURL? {
      if let components = NSURLComponents(string:kSNStockInfoFetchRequestPath) {
        components.queryItems = [NSURLQueryItem(name:"symbol", value:symbol)]
        return components.URL
      }
      return nil
    }
    

    I’ve got another solution:

    http://splinter.com.au/build-a-url-query-string-in-obj-c-from-a-dict

    +(NSString*)urlEscape:(NSString *)unencodedString {
        NSString *s = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
            (CFStringRef)unencodedString,
            NULL,
            (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
            kCFStringEncodingUTF8);
        return [s autorelease]; // Due to the 'create rule' we own the above and must autorelease it
    }
    
    // Put a query string onto the end of a url
    +(NSString*)addQueryStringToUrl:(NSString *)url params:(NSDictionary *)params {
        NSMutableString *urlWithQuerystring = [[[NSMutableString alloc] initWithString:url] autorelease];
        // Convert the params into a query string
        if (params) {
            for(id key in params) {
                NSString *sKey = [key description];
                NSString *sVal = [[params objectForKey:key] description];
                // Do we need to add ?k=v or &k=v ?
                if ([urlWithQuerystring rangeOfString:@"?"].location==NSNotFound) {
                    [urlWithQuerystring appendFormat:@"?%@=%@", [Http urlEscape:sKey], [Http urlEscape:sVal]];
                } else {
                    [urlWithQuerystring appendFormat:@"&%@=%@", [Http urlEscape:sKey], [Http urlEscape:sVal]];
                }
            }
        }
        return urlWithQuerystring;
    }
    

    You can then use it like so:

    NSDictionary *params = @{@"username":@"jim", @"password":@"abc123"};
    
    NSString *urlWithQuerystring = [self addQueryStringToUrl:@"https://myapp.com/login" params:params];
    
    -(NSString*)encodeDictionary:(NSDictionary*)dictionary{
        NSMutableString *bodyData = [[NSMutableString alloc]init];
        int i = 0;
        for (NSString *key in dictionary.allKeys) {
            i++;
            [bodyData appendFormat:@"%@=",key];
            NSString *value = [dictionary valueForKey:key];
            NSString *newString = [value stringByReplacingOccurrencesOfString:@" " withString:@"+"];
            [bodyData appendString:newString];
            if (i < dictionary.allKeys.count) {
                [bodyData appendString:@"&"];
            }
        }
        return bodyData;
    }
    

    I took Joel’s recommendation of using URLQueryItems and turned into a Swift Extension (Swift 3)

    extension URL
    {
        /// Creates an NSURL with url-encoded parameters.
        init?(string : String, parameters : [String : String])
        {
            guard var components = URLComponents(string: string) else { return nil }
    
            components.queryItems = parameters.map { return URLQueryItem(name: $0, value: $1) }
    
            guard let url = components.url else { return nil }
    
            // Kinda redundant, but we need to call init.
            self.init(string: url.absoluteString)
        }
    }
    

    (The self.init method is kinda cheesy, but there was no NSURL init with components)

    Can be used as

    URL(string: "http://www.google.com/", parameters: ["q" : "search me"])
    

    Yet another solution, if you use RestKit there’s a function in RKURLEncodedSerialization called RKURLEncodedStringFromDictionaryWithEncoding that does exactly what you want.

    Simple way of converting NSDictionary to url query string in Objective-c

    Ex: first_name=Steve&middle_name=Gates&last_name=Jobs&address=Palo Alto, California

        NSDictionary *sampleDictionary = @{@"first_name"         : @"Steve",
                                         @"middle_name"          : @"Gates",
                                         @"last_name"            : @"Jobs",
                                         @"address"              : @"Palo Alto, California"};
    
        NSMutableString *resultString = [NSMutableString string];
                for (NSString* key in [sampleDictionary allKeys]){
                    if ([resultString length]>0)
                        [resultString appendString:@"&"];
                    [resultString appendFormat:@"%@=%@", key, [sampleDictionary objectForKey:key]];
                }
    NSLog(@"QueryString: %@", resultString);
    

    Hope will help 🙂