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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
DougW
  • 28,776
  • 18
  • 79
  • 107
  • 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. – Jarret Hardie Dec 27 '09 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. – DougW Dec 28 '09 at 00: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). – n8gray Mar 15 '11 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! – DougW Mar 16 '11 at 21:11

6 Answers6

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
DougW
  • 28,776
  • 18
  • 79
  • 107
  • 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. – DougW Dec 29 '09 at 22:02
  • 4
    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:@" "] – Mirko Froehlich Jan 12 '10 at 21:07
  • Very welcome. And yes, I left matters of encoding/decoding out of these functions. – DougW Jan 14 '10 at 19:36
11

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

Klaas
  • 22,394
  • 11
  • 96
  • 107
10

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

Mike
  • 23,892
  • 18
  • 70
  • 90
  • 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. – DougW Dec 27 '09 at 23:57
  • 2
    [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]. – Jarret Hardie Dec 28 '09 at 01:01
  • Ah perfect, that's what I was looking for, for the path portion at least! – DougW Dec 29 '09 at 21:58
  • 3
    Apple docs for [NSString pathComponents] state: "Note that this method only works with file paths (not, for example, string representations of URLs)." – Jason Moore Jan 26 '10 at 00:43
  • @JasonMoore that's why you pass it the output of `-path` rather than the raw URL string – Mike Abdullah Apr 13 '13 at 08:42
  • 1
    @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. – Jason Moore Apr 15 '13 at 15:09
  • pathComponents work great for RFC 1808 urls, However if you are parsing a mailto it will return nil. – Chris Stephens Jul 31 '13 at 15:34
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);
AlBeebe
  • 8,101
  • 3
  • 51
  • 65
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.

Jorge Israel Peña
  • 36,800
  • 16
  • 93
  • 123
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.

Joe Masilotti
  • 16,815
  • 6
  • 77
  • 87
  • 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 http://stackoverflow.com/questions/1746507/authoritative-position-of-duplicate-http-get-query-keys – DougW Aug 06 '15 at 16:16