Best way to parse URL string to get values for keys?
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¶m2=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¶m2=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)
}
}
Related videos on Youtube
Comments
-
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 withsubstringWithRange:
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 return241
). Or is there just another easier way to parse it than usingNSMakeRange
to get a substring? -
Yetanotherjosh almost 12 yearsThis 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 almost 11 yearsI'd do something like this in the loop to clean things up a bit:
if(pairComponents.count == 2) queryDict[pairComponents[0]] = pairComponents[1];
-
Rick77 almost 11 yearsNice 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 over 10 yearsusing "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 over 10 yearsDo not forget to unescape URL encoding with
stringByReplacingPercentEscapesUsingEncoding
-
adnako over 10 yearsA little optimization:
NSArray *urlComponents = [urlString componentsSeparatedByString:@"&"]; NSMutableDictionary *queryStringDictionary = [[NSMutableDictionary alloc] initWithCapacity: urlComponents.count];
-
Renato Silva Das Neves almost 10 yearsIf 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 almost 9 yearsI 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 almost 9 yearsThis doesn't get the first item.
-
khcpietro over 8 yearsThis 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 over 8 yearsAnd in Swift 2,
func getQueryStringParameter(name: String) -> String? { return self.queryItems?.filter({ (item) in item.name == name }).first?.value }
-
Vaiden over 7 yearsYou have a little typo at the example there - the brackets are worngly placed
-
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 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 over 6 years@Boris For example, decode this URL
http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==
with answer code. It will returnZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ
notZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==
. 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 over 6 years@Aigori yes you're right, but in this case the url wouldn't be
http://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ==
, it would behttp://example.org/?base64=ZGF0YT1bMTUyLCAyNDI2LCAyMzE1XQ%3D%3D
as the value should be url encoded