86

I need to parse a URL string like this one:

&ad_eurl=http://www.youtube.com/video/4bL4FI1Gz6s&hl=it_IT&iv_logging_level=3&ad_flags=0&endscreen_module=http://s.ytimg.com/yt/swfbin/endscreen-vfl6o3XZn.swf&cid=241&cust_gender=1&avg_rating=4.82280613104

I need to split the NSString up into the signle parts like cid=241 and &avg_rating=4.82280613104. I've been doing this with substringWithRange: but the values return in a random order, so that messes it up. Is there any class that allows easy parsing where you can basically convert it to NSDictionary to be able to read the value for a key (for example ValueForKey:cid should return 241). Or is there just another easier way to parse it than using NSMakeRange to get a substring?

Jakub Truhlář
  • 20,070
  • 9
  • 74
  • 84
JonasG
  • 9,274
  • 12
  • 59
  • 88

16 Answers16

174

I also answered this at https://stackoverflow.com/a/26406478/215748.

You can use queryItems in URLComponents.

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.

let url = "http://example.com?param1=value1&param2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)

Alternatively, you can add an extension on URL to make things easier.

extension URL {
    var queryParameters: QueryParameters { return QueryParameters(url: self) }
}

class QueryParameters {
    let queryItems: [URLQueryItem]
    init(url: URL?) {
        queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
        print(queryItems)
    }
    subscript(name: String) -> String? {
        return queryItems.first(where: { $0.name == name })?.value
    }
}

You can then access the parameter by its name.

let url = URL(string: "http://example.com?param1=value1&param2=param2")!
print(url.queryParameters["param1"])
ricardopereira
  • 11,118
  • 5
  • 63
  • 81
Onato
  • 9,916
  • 5
  • 46
  • 54
126

edit (June 2018): this answer is better. Apple added NSURLComponents in iOS 7.

I would create a dictionary, get an array of the key/value pairs with

NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] init];
NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"];

Then populate the dictionary :

for (NSString *keyValuePair in urlComponents)
{
    NSArray *pairComponents = [keyValuePair componentsSeparatedByString:@"="];
    NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
    NSString *value = [[pairComponents lastObject] stringByRemovingPercentEncoding];

    [queryStringDictionary setObject:value forKey:key];
}

You can then query with

[queryStringDictionary objectForKey:@"ad_eurl"];

This is untested, and you should probably do some more error tests.

Thomas Joulin
  • 6,590
  • 9
  • 53
  • 88
  • 13
    This is not URL-decoding the components. It's also not doing bounds checking on the pairComponents array before doing objectAtIndex:1 (what if there is no object at index 1?). – Yetanotherjosh Jun 12 '12 at 18:43
  • I'd do something like this in the loop to clean things up a bit: `if(pairComponents.count == 2) queryDict[pairComponents[0]] = pairComponents[1];` – tybro0103 Jun 14 '13 at 15:03
  • Nice answer, worth point out, though, that it will discard duplicated query keys (it keeps only the last one). The correct approach would be to create a dictionary of NSArray objects. – Rick77 Jul 10 '13 at 10:19
  • 8
    using "componentsSeparatedByString" is not correct for splitting a key from a value in a query string. "=" are quite often included in values (e.g. base64 encoded tokens). – donleyp Oct 22 '13 at 23:10
  • Do not forget to unescape URL encoding with `stringByReplacingPercentEscapesUsingEncoding` – Stephane JAIS Nov 04 '13 at 16:21
  • 1
    A little optimization: `NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"]; NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] initWithCapacity: urlComponents.count];` – adnako Dec 12 '13 at 10:53
  • 3
    If any value contains the character "=" it breaks the populate logic. You could replace part of the code by `NSRange range = [keyValuePair rangeOfString:@"="]; NSString *key = [keyValuePair substringToIndex:range.location]; NSString *value = [keyValuePair substringFromIndex:range.location+1];` – Renato Silva Das Neves Jul 23 '14 at 22:37
  • 1
    This doesn't get the first item. – Keith Adler Jun 29 '15 at 15:29
  • @atulkhatri actually it's been edited since my comment to correct the issues I brought up. I still wouldn't use this though, there are official Apple library methods for this in other answers. – Yetanotherjosh Nov 01 '16 at 22:47
47

I'm a bit late, but the answers provided until now didn't work as I needed to. You can use this code snippet:

NSMutableDictionary *queryStrings = [[NSMutableDictionary alloc] init];
for (NSString *qs in [url.query componentsSeparatedByString:@"&"]) {
    // Get the parameter name
    NSString *key = [[qs componentsSeparatedByString:@"="] objectAtIndex:0];
    // Get the parameter value
    NSString *value = [[qs componentsSeparatedByString:@"="] objectAtIndex:1];
    value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
    value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    queryStrings[key] = value;
}

Where url is the URL you want to parse. You have all of the query strings, escaped, in the queryStrings mutable dictionary.

EDIT: Swift version:

var queryStrings = [String: String]()
if let query = url.query {
    for qs in query.componentsSeparatedByString("&") {
        // Get the parameter name
        let key = qs.componentsSeparatedByString("=")[0]
        // Get the parameter value
        var value = qs.componentsSeparatedByString("=")[1]
        value = value.stringByReplacingOccurrencesOfString("+", withString: " ")
        value = value.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!

        queryStrings[key] = value
    }
}
nikolovski
  • 4,049
  • 1
  • 31
  • 37
  • 2
    This is good answer, but not the best. You should be careful when parameters are something like base64 encoded strings. As @donleyp said, '=' character can be normal value. For example, base64 encoded string needs padding using '=' character. – khcpietro Oct 19 '15 at 08:28
  • @Aigori correct me if I'm wrong, but it seems to me that this answer supports base64. `=` used for padding wouldn't appear as `=` until decoded at the end. It doesn't, however, support query params without a value. – Boris Aug 11 '17 at 20:19
  • @Boris For example, decode this URL `http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==` with answer code. It will return `ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ` not `ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==`. Of course, some base64 decoders decode correctly, but, for example, `NSData initWithBase64EncodedString:options:` returns empty string without padding `=`. Therefore, you should be careful when you handle base64 string with answer code. – khcpietro Aug 14 '17 at 08:31
  • @Aigori yes you're right, but in this case the url wouldn't be `http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==`, it would be `http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ%3D%3D` as the value should be url encoded – Boris Aug 14 '17 at 09:21
10

For iOS8 and above using NSURLComponents :

+(NSDictionary<NSString *, NSString *> *)queryParametersFromURL:(NSURL *)url {
    NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
    NSMutableDictionary<NSString *, NSString *> *queryParams = [NSMutableDictionary<NSString *, NSString *> new];
    for (NSURLQueryItem *queryItem in [urlComponents queryItems]) {
        if (queryItem.value == nil) {
            continue;
        }
        [queryParams setObject:queryItem.value forKey:queryItem.name];
    }
    return queryParams;
}

For iOS 8 below:

+(NSDictionary<NSString *, NSString *> *)queryParametersFromURL:(NSURL *)url    
    NSMutableDictionary<NSString *, NSString *> * parameters = [NSMutableDictionary<NSString *, NSString *> new];
    [self enumerateKeyValuePairsFromQueryString:url.query completionblock:^(NSString *key, NSString *value) {
        parameters[key] = value;
    }];
    return parameters.copy;
}

- (void)enumerateKeyValuePairsFromQueryString:(NSString *)queryString completionBlock:(void (^) (NSString *key, NSString *value))block {
    if (queryString.length == 0) {
        return;
    }
    NSArray *keyValuePairs = [queryString componentsSeparatedByString:@"&"];
    for (NSString *pair in keyValuePairs) {
        NSRange range = [pair rangeOfString:@"="];
        NSString *key = nil;
        NSString *value = nil;

        if (range.location == NSNotFound) {
            key = pair;
            value = @"";
        }
        else {
            key = [pair substringToIndex:range.location];
            value = [pair substringFromIndex:(range.location + range.length)];
        }

        key = [self decodedStringFromString:key];
        key = key ?: @"";

        value = [self decodedStringFromString:value];
        value = value ?: @"";

        block(key, value);
    }
}

+ (NSString *)decodedStringFromString:(NSString *)string {
    NSString *input = shouldDecodePlusSymbols ? [string stringByReplacingOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, string.length)] : string;
    return [input stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
Yatheesha
  • 10,412
  • 5
  • 42
  • 45
8

Swift 5

extension URL {
    func queryParams() -> [String:String] {
        let queryItems = URLComponents(url: self, resolvingAgainstBaseURL: false)?.queryItems
        let queryTuples: [(String, String)] = queryItems?.compactMap{
            guard let value = $0.value else { return nil }
            return ($0.name, value)
        } ?? []
        return Dictionary(uniqueKeysWithValues: queryTuples)
    }
}
Wujo
  • 1,845
  • 2
  • 25
  • 33
6

If you want to do the same thing in swift, you can use an extension.

extension NSURL {
    func queryDictionary() -> [String:String] {
        let components = self.query?.componentsSeparatedByString("&")
        var dictionary = [String:String]()

        for pairs in components ?? [] {
            let pair = pairs.componentsSeparatedByString("=")
            if pair.count == 2 {
                dictionary[pair[0]] = pair[1]
            }
        }

        return dictionary
    }
}
apouche
  • 9,703
  • 6
  • 40
  • 45
5

Since iOS 8 you can directly use properties name and value on NSURLQueryItem.

Example, how to parse URL and get specific values for a key in parsed pairs.

NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:@"someURL" resolvingAgainstBaseURL:false];
NSArray *queryItems = urlComponents.queryItems;
NSMutableArray *someIDs = [NSMutableArray new];
for (NSURLQueryItem *item in queryItems) {
    if ([item.name isEqualToString:@"someKey"]) {
        [someIDs addObject:item.value];
    }
}
NSLog(@"%@", someIDs);
Jakub Truhlář
  • 20,070
  • 9
  • 74
  • 84
4

If you're using NSURLComponents the following concise extension will also do the trick:

extension NSURLComponents {

    func getQueryStringParameter(name: String) -> String? {
        return (self.queryItems? as [NSURLQueryItem])
            .filter({ (item) in item.name == name }).first?
            .value()
    }

}
Gilles De Mey
  • 317
  • 3
  • 10
  • 1
    And in Swift 2, `func getQueryStringParameter(name: String) -> String? { return self.queryItems?.filter({ (item) in item.name == name }).first?.value }` – Jedidja Nov 16 '15 at 16:09
3

This code is work in three cases

1.http://www.youtube.com/watch?v=VWsl7C-y7EI&feature=youtu.be 2.http://youtu.be/lOvcFqQyaDY
3.http://www.youtube.com/watch?v=VWsl7C-y7EI

NSArray *arr = [youtubeurl componentsSeparatedByString:@"v="];
 NSString *youtubeID;
if([arr count]>0)
{
    if([arr count]==1){
        youtubeID= [[youtubeurl componentsSeparatedByString:@"/"] lastObject];

    }
    else{
        NSArray *urlComponents = [[arr objectAtIndex:1] componentsSeparatedByString:@"&"];
        youtubeID=[urlComponents objectAtIndex:0];
    }
}
Rinju Jain
  • 1,694
  • 1
  • 14
  • 23
  • 1
    I was specifically looking for a solution for extracting the YouTube ID and I'd forgotten to take into account the fact that there are multiple variations of valid youtube URL's. +1 Thank you! – Seoras May 20 '15 at 23:57
3

A late solution to this in the form of a Swift 3 extension on URL

extension URL {

  func value(for paramater: String) -> String? {

    let queryItems = URLComponents(string: self.absoluteString)?.queryItems
    let queryItem = queryItems?.filter({$0.name == paramater}).first
    let value = queryItem?.value

    return value
  }

}

gist

Damo
  • 12,840
  • 3
  • 51
  • 62
3
-(NSArray *)getDataOfQueryString:(NSString *)url{
    NSArray *strURLParse = [url componentsSeparatedByString:@"?"];
    NSMutableArray *arrQueryStringData = [[NSMutableArray alloc] init];
    if ([strURLParse count] < 2) {
        return arrQueryStringData;
    }
    NSArray *arrQueryString = [[strURLParse objectAtIndex:1] componentsSeparatedByString:@"&"];

    for (int i=0; i < [arrQueryString count]; i++) {
        NSMutableDictionary *dicQueryStringElement = [[NSMutableDictionary alloc]init];
        NSArray *arrElement = [[arrQueryString objectAtIndex:i] componentsSeparatedByString:@"="];
        if ([arrElement count] == 2) {
            [dicQueryStringElement setObject:[arrElement objectAtIndex:1] forKey:[arrElement objectAtIndex:0]];
        }
        [arrQueryStringData addObject:dicQueryStringElement];
    }

    return arrQueryStringData; 
}

You this function just Pass URL and you will get all the element of querystring.

Crazy Developer
  • 3,464
  • 3
  • 28
  • 62
2

As full function:

+ (NSString *)getQueryComponentWithName:(NSString *)name  fromURL:(NSURL *)url{

NSString *component = nil;
if (url) {
    NSString *query = url.query;

    NSMutableDictionary *queryStringDictionary = [NSMutableDictionary dictionary];
    NSArray *urlComponents = [query componentsSeparatedByString:@"&"];

    for (NSString *keyValuePair in urlComponents){

        NSArray *pairComponents = [keyValuePair componentsSeparatedByString:@"="];
        NSString *key = [[pairComponents firstObject] stringByRemovingPercentEncoding];
        NSString *value = [[pairComponents lastObject] stringByRemovingPercentEncoding];

        [queryStringDictionary setObject:value forKey:key];
    }

    component = [queryStringDictionary objectForKey:name];
}

return component;
}
[self getQueryComponentWithName:@"example" fromURL:[NSURL URLWithString:@"https://google.es/?example=test"]];
93sauu
  • 3,770
  • 3
  • 27
  • 43
2

To get query parameters as a dict:

extension URL {
    var parameters: [String: String] {
        var parameters = [String: String]()
        if let urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: false),
            let queryItems = urlComponents.queryItems {
            for queryItem in queryItems where queryItem.value != nil {
                parameters[queryItem.name] = queryItem.value
            }
        }
        return parameters
    }
}

or return Optional if this more convenient in your case.

0

Query property in the NSURL will give the query string. Then you can parse the query string using componentsSeparatedByString

    NSArray *parameters = [[url query] componentsSeparatedByString:@"&"];

    NSMutableDictionary *keyValuePairs = [NSMutableDictionary dictionary];

    for (NSString *eachParam in parameters)
    {
        NSArray *QryParts = [eachParam componentsSeparatedByString:@"="];
        if ( [QryParts count] == 2 )
        {
            keyValuePairs[QryParts[0]] = QryParts[1];
        }
        else
        {
            keyValuePairs[QryParts[0]] = QryParts[0];
        }
    }

NSString * name = [keyValuePairs valueForKey:@"name"];
NSString * username = [keyValuePairs valueForKey:@"username"];
Joe M
  • 669
  • 6
  • 9
0
- (NSString *)getLoginTokenFromUrl:(NSString *)urlString {
    NSURL *url = [NSURL URLWithString:urlString];
    NSArray *queryStrings = [url.query componentsSeparatedByString:@"&"];

    NSMutableDictionary *queryParams = [[NSMutableDictionary alloc] init];
    for (NSString *qs in queryStrings) {
        // Get the parameter name
        NSArray *components = [qs componentsSeparatedByString:@"="];
        NSString *key = [components objectAtIndex:0];

        // Get the parameter value
        NSString *value;
        if (components.count > 1) {
            value = [components objectAtIndex:1];
        }
        else {
            value = @"";
        }
        value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
        value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

        queryParams[key] = value;
    }

    return [queryParams objectForKey:@"login_token"];
}
Yogesh Maheshwari
  • 1,324
  • 2
  • 16
  • 37
0

A Swift 2 approach:

extension NSURL {

  var queryDictionary: [String: String] {
    var queryDictionary = [String: String]()
    guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false), queryItems = components.queryItems else { return queryDictionary }
    queryItems.forEach { queryDictionary[$0.name] = $0.value }
    return queryDictionary
  }

}

Download Gist

Scott Gardner
  • 8,603
  • 1
  • 44
  • 36