Best way to parse URL string to get values for keys?

97,587

Solution 1

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.

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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)
    }
}
Share:
97,587

Related videos on Youtube

JonasG
Author by

JonasG

iOS app developer.

Updated on April 02, 2020

Comments

  • JonasG
    JonasG about 4 years

    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?

  • Yetanotherjosh
    Yetanotherjosh almost 12 years
    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?).
  • tybro0103
    tybro0103 almost 11 years
    I'd do something like this in the loop to clean things up a bit: if(pairComponents.count == 2) queryDict[pairComponents[0]] = pairComponents[1];
  • Rick77
    Rick77 almost 11 years
    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.
  • donleyp
    donleyp over 10 years
    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).
  • Stephane JAIS
    Stephane JAIS over 10 years
    Do not forget to unescape URL encoding with stringByReplacingPercentEscapesUsingEncoding
  • adnako
    adnako over 10 years
    A little optimization: NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"]; NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] initWithCapacity: urlComponents.count];
  • Renato Silva Das Neves
    Renato Silva Das Neves almost 10 years
    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];
  • Seoras
    Seoras almost 9 years
    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!
  • Keith Adler
    Keith Adler almost 9 years
    This doesn't get the first item.
  • khcpietro
    khcpietro over 8 years
    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.
  • jazzy
    jazzy over 8 years
    And in Swift 2, func getQueryStringParameter(name: String) -> String? { return self.queryItems?.filter({ (item) in item.name == name }).first?.value }
  • Vaiden
    Vaiden over 7 years
    You have a little typo at the example there - the brackets are worngly placed
  • Yetanotherjosh
    Yetanotherjosh over 7 years
    @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.
  • Boris
    Boris over 6 years
    @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.
  • khcpietro
    khcpietro over 6 years
    @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.
  • Boris
    Boris over 6 years
    @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