12

Are there any standard objects or functions to parse an NSURL's components? Clearly I could write one, but why re-invent the wheel?

[NSURL path] will return an NSString like "argX=x&argY=y&argZ=z" What I would rather get back is a dictionary populated with {@"argX" => @"x", @"argY" => @"y", @"argZ" = @"z"}

For the path, which returns a string like "/partA/partB/partC", I would rather get an array with the structure {[0] => @"partA", [1] => @"partB", [2] => @"partC"}

I realize this is a pretty specific ask, but it seems like something a lot of people would want.

This is for iOS! Apparently NSURL has different functions on macOS.

4
  • Good question. Do you mean: If the URL is "/partA/partB/partC", you want "0 => partA, 1=> partB, 2=>partC". If the URL is "/foo?partA=A&partB=B" you want "partA => A, partB => B". What if the URL is "/partA/partB/partC?foo=bar&ooga=booga"? In one example, the important information needed to identify the entity in question is being passed in a query string... in the other example, the identifying information is passed rest-style in the path itself. Commented Dec 27, 2009 at 23:53
  • I mean I want to parse the path and the query separately. It should not be one function that combines those two tasks. See the code I posted below for clarification. If someone answers with an existing implementation, I'll be happy to still accept that. Commented Dec 28, 2009 at 0:04
  • One thing to keep in mind is that this is a perfectly valid query string: "x=1&x=0&x=foo", so an NSDictionary might not be the best representation (unless you're using arrays as the values). Commented Mar 15, 2011 at 16:38
  • @n8gray - You're right, my answer below doesn't deal with duplicate values. I would avoid duplicate values like the plague in any real-world scenarios though, because many servers (PHP servers for example) will end up with only one value for x... x=>"foo", just like my script. PHP will however, build an array out of "x[]=1&x[]=foo", which my answer will not do. It should be a trivial exercise to expand my example to build arrays if the left hand parameter name ends in "[]" though, so I'll leave that for anyone who needs it to add. Thanks! Commented Mar 16, 2011 at 21:11

6 Answers 6

12

Alright, I got antsy and wrote a solution for extending NSString through Categories. I haven't tested this yet, but if you want to use it, go for it.

@interface NSString (ParseCategory)
- (NSMutableDictionary *)explodeToDictionaryInnerGlue:(NSString *)innerGlue outterGlue:(NSString *)outterGlue;
@end

@implementation NSString (ParseCategory)

- (NSMutableDictionary *)explodeToDictionaryInnerGlue:(NSString *)innerGlue outterGlue:(NSString *)outterGlue {
    // Explode based on outter glue
    NSArray *firstExplode = [self componentsSeparatedByString:outterGlue];
    NSArray *secondExplode;

    // Explode based on inner glue
    NSInteger count = [firstExplode count];
    NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithCapacity:count];
    for (NSInteger i = 0; i < count; i++) {
        secondExplode = [(NSString *)[firstExplode objectAtIndex:i] componentsSeparatedByString:innerGlue];
        if ([secondExplode count] == 2) {
            [returnDictionary setObject:[secondExplode objectAtIndex:1] forKey:[secondExplode objectAtIndex:0]];
        }
    }

    return returnDictionary;
}

@end

It's called like this:

NSMutableDictionary *parsedQuery = [[myNSURL query] explodeToDictionaryInnerGlue:@"=" outterGlue=@"&"]

For parsing the path portion of the NSURL (ie @"/partA/partB/partC"), just call this:

NSArray *parsedPath = [[nyNSURL path] componentsSeperatedByString:@"/"];

Be aware that parsedPath[0] will be an empty string because of the leading /!

EDIT - Here is a Category extension to NSURL for your usage pleasure. It strips the initial "/" so you don't have an empty 0 index.

@implementation NSURL (ParseCategory)

- (NSArray *)pathArray {
    // Create a character set for the slash character
    NSRange slashRange;
    slashRange.location = (unsigned int)'/';
    slashRange.length = 1;
    NSCharacterSet *slashSet = [NSCharacterSet characterSetWithRange:slashRange];

    // Get path with leading (and trailing) slashes removed
    NSString *path = [[self path] stringByTrimmingCharactersInSet:slashSet];

    return [path componentsSeparatedByCharactersInSet:slashSet];
}

- (NSDictionary *)queryDictionary {
    NSDictionary *returnDictionary = [[[[self query] explodeToDictionaryInnerGlue:@"=" outterGlue:@"&"] copy] autorelease];
    return returnDictionary;
}

@end
Sign up to request clarification or add additional context in comments.

3 Comments

Not sure why someone down-voted this... that'll teach me to share code I guess? I wouldn't really mind if you'd left a comment, but I'll just assume you're a random troll. Hi troll.
Thanks, this works fine for me. Note that you may need to URL-decode your individual query string values as well. I am using the following code for this, first to take care of percent-escapes, then to replace "+" with space, which the first one unfortunately doesn't do: [[value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@"+" withString:@" "]
Very welcome. And yes, I left matters of encoding/decoding out of these functions.
11

Sam Soffes created a well maintained Category for NSURL / NSDictionary. It can be found here: https://github.com/samsoffes/sstoolkit/

Comments

10

You might want to look at pathComponents which returns an array of the components of the URL. Get more information here.

7 Comments

I don't believe this is available on the iphone version of the SDK. I should have clarified that I'm working on iphone OS.
[NSString pathComponents] is available in both the Cocoa and Cocoa Touch APIs. On the iPhone, you can imitate OS X 10.6's [NSURL pathComponents] with [[NSURL path] pathComponents].
Ah perfect, that's what I was looking for, for the path portion at least!
Apple docs for [NSString pathComponents] state: "Note that this method only works with file paths (not, for example, string representations of URLs)."
@MikeAbdullah: Ah, ok. For anyone else stumbling on this 3-year old question: As of iOS4 you no longer need to convert the URL to a string and can just use: -[NSURL pathComponents] directly.
|
5

Heres what i use to parse the query string

// Parse the individual parameters
// parameters = @"hello=world&foo=bar";
NSMutableDictionary *dictParameters = [[NSMutableDictionary alloc] init];
NSArray *arrParameters = [parameters componentsSeparatedByString:@"&"];
for (int i = 0; i < [arrParameters count]; i++) {
    NSArray *arrKeyValue = [[arrParameters objectAtIndex:i] componentsSeparatedByString:@"="];
    if ([arrKeyValue count] >= 2) {
        NSMutableString *strKey = [NSMutableString stringWithCapacity:0];
        [strKey setString:[[[arrKeyValue objectAtIndex:0] lowercaseString] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
        NSMutableString *strValue   = [NSMutableString stringWithCapacity:0];
        [strValue setString:[[[arrKeyValue objectAtIndex:1]  stringByReplacingOccurrencesOfString:@"+" withString:@" "] stringByReplacingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
        if (strKey.length > 0) [dictParameters setObject:strValue forKey:strKey];
    }
}
NSLog(@"Parameters: %@", dictParameters);

Comments

2

If you do decide to write one (I'm not sure there are existing methods of getting the components you want), you might want to use NSString's componentsSeparatedByString.

Comments

2

Introduced in iOS 8 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.

You can retrieve the query items from a URL by first creating NSURLComponents:

NSURL *url = [NSURL URLWithString:@"http://stackoverflow.com?q=ios&count=10"];
NSURLComponents *components = [NSURLComponents componentsWithURL:url 
                                               resolvingAgainstBaseURL:YES];
for (NSURLQueryItem *item in components.queryItems) {
    NSLog(@"name: %@, value: %@", item.name, item.value);
}
// name: q, value: ios
// name: count, value: 10

Note that they return value for -queryItems is an array, not a dictionary. This is because the following is a valid URL. Note the two identical "keys", foo.

http://google.com?foo=bar&foo=baz

To create a URL via query items, use the designated initializer queryItemWithName:value: and then add them to NSURLComponents to generate an NSURL. For example:

NSString *urlString = @"http://stackoverflow.com";
NSURLComponents *components = [NSURLComponents componentsWithString:urlString];
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) {
    NSURLQueryItem *item = [NSURLQueryItem queryItemWithName:key 
                                                       value:queryDictionary[key]]; 
    [queryItems addObject:item];
}
components.queryItems = queryItems;

I've also written a blog post with more details, Building NSURLs with NSURLQueryItems.

1 Comment

Cool! Just a note to devs--if you use duplicate GET parameters you're in for a world of hurt. There is no defined spec for how to handle those, and different languages/platforms make up their own rules. See stackoverflow.com/questions/1746507/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.