Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue-89 - Add support for arrays in query parameters #98

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DeepLinkKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
DEE40E5F1A42B0530097EA33 /* DPLActionDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE40E5D1A42ABE80097EA33 /* DPLActionDataSource.m */; };
DEE40E601A42B0580097EA33 /* DPLDemoAction.m in Sources */ = {isa = PBXBuildFile; fileRef = DEE40E581A42A3F50097EA33 /* DPLDemoAction.m */; };
DEEBD4A91AAB7946000BCA84 /* DPLSerializableObject.m in Sources */ = {isa = PBXBuildFile; fileRef = DEEBD4A81AAB7946000BCA84 /* DPLSerializableObject.m */; };
E9A189201CB6E6600029E56B /* DPLArrayRouteHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9A1891F1CB6E6600029E56B /* DPLArrayRouteHandler.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -190,6 +191,8 @@
DEEBD4A71AAB7946000BCA84 /* DPLSerializableObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DPLSerializableObject.h; path = Fixtures/DPLSerializableObject.h; sourceTree = "<group>"; };
DEEBD4A81AAB7946000BCA84 /* DPLSerializableObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DPLSerializableObject.m; path = Fixtures/DPLSerializableObject.m; sourceTree = "<group>"; };
DF9272621ECB6C2824AD5C94 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ./LICENSE; sourceTree = "<group>"; };
E989F5AC1CB709B8005D51D6 /* NSString+DPLQuery_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+DPLQuery_Private.h"; sourceTree = "<group>"; };
E9A1891F1CB6E6600029E56B /* DPLArrayRouteHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DPLArrayRouteHandler.swift; sourceTree = "<group>"; };
E9CA1DB95577CF3689F4B77F /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ./README.md; sourceTree = "<group>"; };
EC3E153594BC11ACCA16FACF /* Pods_ReceiverDemoSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ReceiverDemoSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; };
FB774EA6D84A50233F032ADC /* Pods-ReceiverDemoSwift.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReceiverDemoSwift.test.xcconfig"; path = "Pods/Target Support Files/Pods-ReceiverDemoSwift/Pods-ReceiverDemoSwift.test.xcconfig"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -343,6 +346,7 @@
children = (
62EE96F31B570891003D7564 /* DPLProductRouteHandler.swift */,
62891E8A1B57FE5A00C2AF4F /* DPLMessageRouteHandler.swift */,
E9A1891F1CB6E6600029E56B /* DPLArrayRouteHandler.swift */,
);
path = RouteHandlers;
sourceTree = "<group>";
Expand Down Expand Up @@ -470,6 +474,7 @@
DE16E91C1A42720D00077E18 /* NSString+DPLTrim.h */,
DE16E91B1A42720D00077E18 /* NSString+DPLTrim.m */,
DE058E071A3B46F500147C04 /* NSString+DPLQuery.h */,
E989F5AC1CB709B8005D51D6 /* NSString+DPLQuery_Private.h */,
DE058E081A3B46F500147C04 /* NSString+DPLQuery.m */,
DE3E61081A3B4485008D6DFC /* NSString+DPLJSON.h */,
DE3E61091A3B4485008D6DFC /* NSString+DPLJSON.m */,
Expand Down Expand Up @@ -1076,6 +1081,7 @@
buildActionMask = 2147483647;
files = (
62EE96F61B570B10003D7564 /* DPLProductDataSource.m in Sources */,
E9A189201CB6E6600029E56B /* DPLArrayRouteHandler.swift in Sources */,
62EE96F51B570AF4003D7564 /* DPLProductDetailViewController.m in Sources */,
62335E031B57007F00E3818C /* DPLReceiverSwiftAppDelegate.swift in Sources */,
62891E8B1B57FE5A00C2AF4F /* DPLMessageRouteHandler.swift in Sources */,
Expand Down
21 changes: 21 additions & 0 deletions DeepLinkKit/Categories/NSObject+DPLJSONObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,24 @@ - (id)DPL_JSONObject {
}

@end


@implementation NSOrderedSet (DPLJSONObject)

- (id)DPL_JSONObject {
if ([NSJSONSerialization isValidJSONObject:self]) {
return self;
}

NSMutableOrderedSet *mutableSet = [NSMutableOrderedSet orderedSet];

for (id value in self) {
if (![value isEqual:[NSNull null]]) {
[mutableSet addObject:[value DPL_JSONObject]];
}
}

return [NSOrderedSet orderedSetWithOrderedSet:mutableSet];
}

@end
26 changes: 24 additions & 2 deletions DeepLinkKit/Categories/NSString+DPLQuery.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
@import Foundation;


/// Key to get query parameters dictionary from the result of `- (NSDictionary *)DPL_parametersDictionaryAndOrderFromQueryString'
static NSString * const DPL_ParametersValuesDictionaryKey = @"DPL_ParametersValuesDictionaryKey";


// Key to get query ordered parameter names set from the result of `- (NSDictionary *)DPL_parametersDictionaryAndOrderFromQueryString'
static NSString * const DPL_OrderedParameterNamesSetKey = @"DPL_OrderedParameterNamesSetKey";


@interface NSString (DPLQuery)


Expand All @@ -16,11 +25,24 @@
+ (NSString *)DPL_queryStringWithParameters:(NSDictionary *)parameters;


/**
Returns a percent encoded query string from a dictionary of parameters.
@param parameters A dictionary of parameters.
@param orderedParameterNames An ordered set of parameter names.
@note if number of parameters in `orderedParameterNames' is less then number of keys in `parameters'
or `orderedParameterNames' is nill then parameters are sorted using `localizedCaseInsensitiveCompare:'.
@return String containing parameters.
*/
+ (NSString *)DPL_queryStringWithParameters:(NSDictionary *)parameters orderedParameterNames:(NSOrderedSet *)orderedParameterNames;


/**
Parses the receiver (when formatted as a query string) into an NSDictionary.
@return An NSDictionary of parameters parsed from the receiver.
@return A NSDictionary which contains two key-value pairs:
- NSDictionary of parameters keys and values parsed from the receiver (DPL_ParametersValuesDictionaryKey)
- NSOrderedSet of ordered parameter names parsed from the receiver (DPL_OrderedParameterNamesSetKey).
*/
- (NSDictionary *)DPL_parametersFromQueryString;
- (NSDictionary *)DPL_parametersDictionaryAndOrderFromQueryString;



Expand Down
102 changes: 88 additions & 14 deletions DeepLinkKit/Categories/NSString+DPLQuery.m
Original file line number Diff line number Diff line change
@@ -1,37 +1,96 @@
#import "NSString+DPLQuery.h"

static NSString * const DPL_ArrayLiteral = @"[]";

@implementation NSString (UBRQuery)

+ (NSString *)DPL_queryStringWithParameters:(NSDictionary *)parameters {
return [self DPL_queryStringWithParameters:parameters orderedParameterNames:nil];
}


+ (NSString *)DPL_queryStringWithParameters:(NSDictionary *)parameters orderedParameterNames:(NSOrderedSet *)orderedParameterNames {
NSArray *orderedParametersArray;
if (orderedParameterNames && orderedParameterNames.count == [parameters allKeys].count) {
orderedParametersArray = [orderedParameterNames array];
}
else {
orderedParametersArray = [[parameters allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}

NSMutableString *query = [NSMutableString string];
[[parameters allKeys] enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
NSString *value = [parameters[key] description];
key = [key DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
value = [value DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[query appendFormat:@"%@%@%@%@", (idx > 0) ? @"&" : @"", key, (value.length > 0) ? @"=" : @"", value];
NSString *percentEscapedArrayLiteral = [DPL_ArrayLiteral DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[orderedParametersArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) {
NSString *value = @"";
if ([parameters[key] isKindOfClass:[NSArray class]]) {
NSString *keyValuePair = nil;
NSString *arrayValueString = nil;
for (NSString *arrayValue in parameters[key]) {
arrayValueString = [arrayValue.description DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
key = [key DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
keyValuePair = [NSString stringWithFormat:@"%@%@=%@", key, percentEscapedArrayLiteral, arrayValueString];
keyValuePair = [NSString stringWithFormat:@"%@%@", (value.length > 0) ? @"&" : @"", keyValuePair];
value = [value stringByAppendingString:keyValuePair];
}
if (value.length == 0) {
value = [NSString stringWithFormat:@"%@%@", key, percentEscapedArrayLiteral];
}
key = @"";
}
else {
value = [parameters[key] description];
key = [key DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
value = [value DPL_stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
}
[query appendFormat:@"%@%@%@%@", (idx > 0) ? @"&" : @"", key, (value.length > 0 && key.length > 0) ? @"=" : @"", value];
}];
return [query copy];
}


- (NSDictionary *)DPL_parametersFromQueryString {
- (NSDictionary *)DPL_parametersDictionaryAndOrderFromQueryString {
NSArray *params = [self componentsSeparatedByString:@"&"];
NSMutableDictionary *paramsDict = [NSMutableDictionary dictionaryWithCapacity:[params count]];
NSMutableOrderedSet *orderedParameters = [NSMutableOrderedSet orderedSet];
NSString *key;
NSObject *value;
NSArray *pairs;
for (NSString *param in params) {
NSArray *pairs = [param componentsSeparatedByString:@"="];
pairs = [param componentsSeparatedByString:@"="];
if (pairs.count == 2) {
// e.g. ?key=value
NSString *key = [pairs[0] DPL_stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *value = [pairs[1] DPL_stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
paramsDict[key] = value;
key = [pairs[0] DPL_stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
value = [pairs[1] DPL_stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
if ([key DPL_containsArrayLiteral]) {
// e.g. ?items[]=item1&items[]=item2
key = [key DPL_stringByRemovingArrayLiteral];
if (!paramsDict[key]) {
paramsDict[key] = @[value];
}
else {
paramsDict[key] = [paramsDict[key] arrayByAddingObject:value];
}
}
else {
paramsDict[key] = value;
}
}
else if (pairs.count == 1) {
// e.g. ?key
NSString *key = [[pairs firstObject] DPL_stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
paramsDict[key] = @"";
key = [[pairs firstObject] DPL_stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
if ([key DPL_containsArrayLiteral]) {
// e.g. ?items[]
key = [key DPL_stringByRemovingArrayLiteral];
value = @[];
}
else {
// e.g. ?key
value = @"";
}
paramsDict[key] = value;
}
[orderedParameters addObject:key];
}
return [paramsDict copy];
return @{ DPL_ParametersValuesDictionaryKey: [paramsDict copy], DPL_OrderedParameterNamesSetKey: [orderedParameters copy] };
}


Expand All @@ -47,4 +106,19 @@ - (NSString *)DPL_stringByReplacingPercentEscapesUsingEncoding:(NSStringEncoding
return [self stringByRemovingPercentEncoding];
}


#pragma mark - Array Literals

- (BOOL)DPL_containsArrayLiteral {
return [self hasSuffix:DPL_ArrayLiteral];
}


- (NSString *)DPL_stringByRemovingArrayLiteral {
if ([self DPL_containsArrayLiteral]) {
return [self substringWithRange:NSMakeRange(0, self.length - DPL_ArrayLiteral.length)];
}
return [self copy];
}

@end
25 changes: 25 additions & 0 deletions DeepLinkKit/Categories/NSString+DPLQuery_Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#import "NSString+DPLQuery.h"

@interface NSString (DPLQuery_Private)



///---------------------
/// @name Array Literals
///---------------------


/**
Checks if string ends with array literal ('[]')
@return YES if string ends with '[]', NO otherwise
*/
- (BOOL)DPL_containsArrayLiteral;


/**
Removes array literal ('[]') from the string
@return String without array literal ('[]')
*/
- (NSString *)DPL_stringByRemovingArrayLiteral;

@end
7 changes: 7 additions & 0 deletions DeepLinkKit/DeepLink/DPLDeepLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
@property (nonatomic, copy, readonly) NSDictionary *queryParameters;


/**
The order of query parameter names parsed from the incoming URL.
@note If the URL conforms to the App Link standard, this will be the order of query parameters found on `target_url'.
*/
@property (nonatomic, copy, readonly) NSOrderedSet *orderedParameterNames;


/**
A dictionary of values keyed by their parameterized route component matched in the deep link URL path.
@note Given a route `alert/:title/:message' and a path `button://alert/hello/world',
Expand Down
10 changes: 7 additions & 3 deletions DeepLinkKit/DeepLink/DPLDeepLink.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ - (instancetype)initWithURL:(NSURL *)url {

self = [super init];
if (self) {

_URL = url;
_queryParameters = [[_URL query] DPL_parametersFromQueryString];

_URL = url;
NSDictionary *parametersAndOrder = [[_URL query] DPL_parametersDictionaryAndOrderFromQueryString];
_queryParameters = parametersAndOrder[DPL_ParametersValuesDictionaryKey];
_orderedParameterNames = parametersAndOrder[DPL_OrderedParameterNamesSetKey];

NSMutableDictionary *mutableQueryParams = [_queryParameters mutableCopy];
NSArray *JSONEncodedFields = [mutableQueryParams[DPLJSONEncodedFieldNamesKey] DPL_decodedJSONObject];
Expand Down Expand Up @@ -56,13 +58,15 @@ - (NSString *)description {
@"\n<%@ %p\n"
@"\t URL: \"%@\"\n"
@"\t queryParameters: \"%@\"\n"
@"\t orderedParameterNames: \"%@\"\n"
@"\t routeParameters: \"%@\"\n"
@"\t callbackURL: \"%@\"\n"
@">",
NSStringFromClass([self class]),
self,
[self.URL description],
self.queryParameters,
self.orderedParameterNames,
self.routeParameters,
[self.callbackURL description]];
}
Expand Down
4 changes: 4 additions & 0 deletions DeepLinkKit/DeepLink/DPLMutableDeepLink.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
@property (nonatomic, copy, readwrite) NSMutableDictionary *queryParameters;


/// The ordered parameter names from the query as a mutable ordered set. Default is empty.
@property (nonatomic, copy, readwrite) NSMutableOrderedSet *orderedParameterNames;


/// A URL object derived from the mutable deep link components.
@property (nonatomic, copy, readonly) NSURL *URL;

Expand Down
Loading