Search code examples
iosafnetworking-2

AFNetworking 2.5.3 Serialization of array to send indexes explicitely


I use

AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer]; 

and this sends out the call to the server when having an array in the format:

payments[]
payments[][email]=0&
payments[][category]=&

payments[][email]=1&
payments[][category]=&

I need the indexes for the server like.

payments[]
payments[0][email]=0&
payments[0][category]=&

payments[1][email]=1&
payments[1][category]=&

How can I achieve that?

Thanks.


Solution

  • AFHTTPRequestSerializer has a method setQueryStringSerializationWithBlock which allows you to provide your own block for serializing the parameters.

    Unfortunately, the internal AF* functions for serialization are private, but you can copy them and make a small modification in these lines to add the indexes.

    To set your own serialization block:

    [serializer setQueryStringSerializationWithBlock:^NSString *(NSURLRequest *request, id parameters, NSError *__autoreleasing *error) {
        return ZAFQueryStringFromParametersWithEncoding(parameters, serializer.stringEncoding);
    }];
    

    Including the code, where the AFNetworking functions have been copied and prefixed with a Z to ZAF:

    static NSString * const kAFCharactersToBeEscapedInQueryString = @":/?&=;+!@#$()',*";
    
    static NSString * AFPercentEscapedQueryStringKeyFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
        static NSString * const kAFCharactersToLeaveUnescapedInQueryStringPairKey = @"[].";
    
        return (__bridge_transfer  NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kAFCharactersToLeaveUnescapedInQueryStringPairKey, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
    }
    
    static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
        return (__bridge_transfer  NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding));
    }
    
    #pragma mark -
    
    @interface ZAFQueryStringPair : NSObject
    @property (readwrite, nonatomic, strong) id field;
    @property (readwrite, nonatomic, strong) id value;
    
    - (id)initWithField:(id)field value:(id)value;
    
    - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding;
    @end
    
    @implementation ZAFQueryStringPair
    
    - (id)initWithField:(id)field value:(id)value {
        self = [super init];
        if (!self) {
            return nil;
        }
    
        self.field = field;
        self.value = value;
    
        return self;
    }
    
    - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding {
        if (!self.value || [self.value isEqual:[NSNull null]]) {
            return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding);
        } else {
            return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)];
        }
    }
    
    @end
    
    #pragma mark -
    
    static NSArray * ZAFQueryStringPairsFromDictionary(NSDictionary *dictionary);
    static NSArray * ZAFQueryStringPairsFromKeyAndValue(NSString *key, id value);
    
    static NSString * ZAFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
        NSMutableArray *mutablePairs = [NSMutableArray array];
        for (ZAFQueryStringPair *pair in ZAFQueryStringPairsFromDictionary(parameters)) {
            [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];
        }
    
        return [mutablePairs componentsJoinedByString:@"&"];
    }
    
    NSArray * ZAFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
        return ZAFQueryStringPairsFromKeyAndValue(nil, dictionary);
    }
    
    NSArray * ZAFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
        NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
    
        NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
    
        if ([value isKindOfClass:[NSDictionary class]]) {
            NSDictionary *dictionary = value;
            // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
            for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
                id nestedValue = [dictionary objectForKey:nestedKey];
                if (nestedValue) {
                    [mutableQueryStringComponents addObjectsFromArray:ZAFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
                }
            }
        } else if ([value isKindOfClass:[NSArray class]]) {
            NSArray *array = value;
            NSInteger idx = 0;
            for (id nestedValue in array) {
                [mutableQueryStringComponents addObjectsFromArray:ZAFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[%ld]", key, idx++], nestedValue)];
            }
        } else if ([value isKindOfClass:[NSSet class]]) {
            NSSet *set = value;
            for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
                [mutableQueryStringComponents addObjectsFromArray:ZAFQueryStringPairsFromKeyAndValue(key, obj)];
            }
        } else {
            [mutableQueryStringComponents addObject:[[ZAFQueryStringPair alloc] initWithField:key value:value]];
        }
    
        return mutableQueryStringComponents;
    }