Creating URL query parameters from NSDictionary objects in ObjectiveC

Objective CNsurl

Objective C Problem Overview


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.

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?

Objective C Solutions


Solution 1 - Objective C

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.

Solution 2 - Objective C

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>;.

Solution 3 - Objective C

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

Solution 4 - Objective C

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

Solution 5 - Objective C

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);
}

Solution 6 - Objective C

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

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

Solution 7 - Objective C

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
}

Solution 8 - Objective C

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"])

Solution 9 - Objective C

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];

Solution 10 - Objective C

-(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;
}

Solution 11 - Objective C

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

Solution 12 - Objective C

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 :)

Solution 13 - Objective C

If you are already using AFNetwork, you can use their built in serializer to to produce an encoded URL;

NSString *baseURL = @"https://api.app.com/parse";
NSDictionary *mutableParameters = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"true",@"option1", data, @"option2", token, @"token", @"3.0", @"app", nil];
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:baseURL parameters:mutableParameters error:nil];
NSString *urlPath = request.URL.absoluteString;
NSLog(@"%@", urlPath); // https://api.app.com/parse?option1=true&option2=datavalue&token=200%3ATEST%3AENCODE ....

Note; this is an extension to an above answer. The edit queue is full so cannot be added to the existing answer.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser87229View Question on Stackoverflow
Solution 1 - Objective CJoe MasilottiView Answer on Stackoverflow
Solution 2 - Objective CDon McCaugheyView Answer on Stackoverflow
Solution 3 - Objective CAnonymous DevView Answer on Stackoverflow
Solution 4 - Objective CAlBeebeView Answer on Stackoverflow
Solution 5 - Objective CRenetikView Answer on Stackoverflow
Solution 6 - Objective CAyush GoelView Answer on Stackoverflow
Solution 7 - Objective CZorayrView Answer on Stackoverflow
Solution 8 - Objective CCanView Answer on Stackoverflow
Solution 9 - Objective CChrisView Answer on Stackoverflow
Solution 10 - Objective CFilippo Ozino CaligarisView Answer on Stackoverflow
Solution 11 - Objective CcicerocamargoView Answer on Stackoverflow
Solution 12 - Objective CvidalbenjoeView Answer on Stackoverflow
Solution 13 - Objective CRalpharooView Answer on Stackoverflow