Add support for mapping attributes to deeply nested keyPaths on NSMutableDictionary. fixes #882

This commit is contained in:
Blake Watters
2012-10-15 22:00:14 -04:00
parent 845de91e80
commit 64e9c7cb6d
3 changed files with 73 additions and 26 deletions

View File

@@ -98,7 +98,7 @@
return self.rootKeyPath ? [NSMutableDictionary dictionaryWithObject:dictionary forKey:self.rootKeyPath] : dictionary;
}
#pragma mark - RKObjectMappingOperationDelegate
#pragma mark - RKMappingOperationDelegate
- (void)mappingOperation:(RKMappingOperation *)operation didSetValue:(id)value forKeyPath:(NSString *)keyPath usingMapping:(RKAttributeMapping *)mapping
{

View File

@@ -49,6 +49,23 @@ extern NSString * const RKObjectMappingNestingAttributeKeyName;
// Defined in RKObjectMapping.h
NSDate *RKDateFromStringWithFormatters(NSString *dateString, NSArray *formatters);
/**
This method ensures that attribute mappings apply cleanly to an `NSMutableDictionary` target class to support mapping to nested keyPaths. See issue #882
*/
static void RKSetIntermediateDictionaryValuesOnObjectForKeyPath(id object, NSString *keyPath)
{
if (! [object isKindOfClass:[NSMutableDictionary class]]) return;
NSArray *keyPathComponents = [keyPath componentsSeparatedByString:@"."];
if ([keyPathComponents count] > 1) {
for (NSUInteger index = 0; index < [keyPathComponents count] - 1; index++) {
NSString *intermediateKeyPath = [[keyPathComponents subarrayWithRange:NSMakeRange(0, index + 1)] componentsJoinedByString:@"."];
if (! [object valueForKeyPath:intermediateKeyPath]) {
[object setValue:[NSMutableDictionary dictionary] forKeyPath:intermediateKeyPath];
}
}
}
}
@implementation RKMappingOperation
- (id)initWithSourceObject:(id)sourceObject destinationObject:(id)destinationObject mapping:(RKMapping *)objectOrDynamicMapping
@@ -269,10 +286,12 @@ NSDate *RKDateFromStringWithFormatters(NSString *dateString, NSArray *formatters
value = [self transformValue:value atKeyPath:attributeMapping.sourceKeyPath toType:type];
}
RKSetIntermediateDictionaryValuesOnObjectForKeyPath(self.destinationObject, attributeMapping.destinationKeyPath);
// Ensure that the value is different
if ([self shouldSetValue:&value atKeyPath:attributeMapping.destinationKeyPath]) {
RKLogTrace(@"Mapped attribute value from keyPath '%@' to '%@'. Value: %@", attributeMapping.sourceKeyPath, attributeMapping.destinationKeyPath, value);
[self.destinationObject setValue:value forKeyPath:attributeMapping.destinationKeyPath];
if ([self.delegate respondsToSelector:@selector(mappingOperation:didSetValue:forKeyPath:usingMapping:)]) {
[self.delegate mappingOperation:self didSetValue:value forKeyPath:attributeMapping.destinationKeyPath usingMapping:attributeMapping];

View File

@@ -54,8 +54,8 @@
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:object requestDescriptor:requestDescriptor error:&error];
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeFormURLEncoded error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"key2-form-name=value2&key1-form-name=value1")));
expect(error).to.beNil();
expect(string).to.equal(@"key2-form-name=value2&key1-form-name=value1");
}
- (void)testShouldSerializeADate
@@ -68,8 +68,8 @@
NSError *error = nil;
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[NSDictionary class] rootKeyPath:nil];
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:object requestDescriptor:requestDescriptor error:&error];
assertThat(error, is(nilValue()));
assertThat(parameters[@"date-form-name"], is(equalTo(@"1970-01-01 00:00:00 +0000")));
expect(error).to.beNil();
expect(parameters[@"date-form-name"]).to.equal(@"1970-01-01 00:00:00 +0000");
}
- (void)testShouldSerializeADateToAStringUsingThePreferredDateFormatter
@@ -89,8 +89,8 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeFormURLEncoded error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"key1-form-name=value1&date-form-name=01/01/1970")));
expect(error).to.beNil();
expect(string).to.equal(@"key1-form-name=value1&date-form-name=01/01/1970");
}
- (void)testShouldSerializeADateToJSON
@@ -107,8 +107,8 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"{\"key1-form-name\":\"value1\",\"date-form-name\":\"1970-01-01 00:00:00 +0000\"}")));
expect(error).to.beNil();
expect(string).to.equal(@"{\"key1-form-name\":\"value1\",\"date-form-name\":\"1970-01-01 00:00:00 +0000\"}");
}
- (void)testShouldSerializeNSDecimalNumberAttributesToJSON
@@ -125,8 +125,8 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"{\"key1-form-name\":\"value1\",\"number-form-name\":\"18274191731731.4557723623\"}")));
expect(error).to.beNil();
expect(string).to.equal(@"{\"key1-form-name\":\"value1\",\"number-form-name\":\"18274191731731.4557723623\"}");
}
- (void)testShouldSerializeRelationshipsToo
@@ -151,11 +151,11 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeFormURLEncoded error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
expect(error).to.beNil();
#if TARGET_OS_IPHONE
assertThat(string, is(equalTo(@"key1-form-name=value1&relationship1-form-name[r1k1][]=relationship1Value1&relationship1-form-name[r1k1][]=relationship1Value2&key2-form-name=value2&relationship2-form-name[subKey1]=subValue1")));
expect(string).to.equal(@"key1-form-name=value1&relationship1-form-name[r1k1][]=relationship1Value1&relationship1-form-name[r1k1][]=relationship1Value2&key2-form-name=value2&relationship2-form-name[subKey1]=subValue1");
#else
assertThat(string, is(equalTo(@"relationship1-form-name[r1k1][]=relationship1Value1&relationship1-form-name[r1k1][]=relationship1Value2&key2-form-name=value2&key1-form-name=value1&relationship2-form-name[subKey1]=subValue1")));
expect(string).to.equal(@"relationship1-form-name[r1k1][]=relationship1Value1&relationship1-form-name[r1k1][]=relationship1Value2&key2-form-name=value2&key1-form-name=value1&relationship2-form-name[subKey1]=subValue1");
#endif
}
@@ -173,8 +173,8 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"{\"key2-form-name\":\"value2\",\"key1-form-name\":\"value1\"}")));
expect(error).to.beNil();
expect(string).to.equal(@"{\"key2-form-name\":\"value2\",\"key1-form-name\":\"value1\"}");
}
- (void)testShouldSetReturnEmptyDictionaryIfItDoesNotFindAnythingToSerialize
@@ -227,9 +227,9 @@
// Encodes differently on iOS / OS X
#if TARGET_OS_IPHONE
assertThat(string, is(equalTo(@"{\"stringTest\":\"The string\",\"hasOne\":{\"date\":\"1970-01-01 00:00:00 +0000\"}}")));
expect(string).to.equal(@"{\"stringTest\":\"The string\",\"hasOne\":{\"date\":\"1970-01-01 00:00:00 +0000\"}}");
#else
assertThat(string, is(equalTo(@"{\"hasOne\":{\"date\":\"1970-01-01 00:00:00 +0000\"},\"stringTest\":\"The string\"}")));
expect(string).to.equal(@"{\"hasOne\":{\"date\":\"1970-01-01 00:00:00 +0000\"},\"stringTest\":\"The string\"}");
#endif
}
@@ -244,8 +244,8 @@
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[NSDictionary class] rootKeyPath:@"stuff"];
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:object requestDescriptor:requestDescriptor error:&error];
assertThat(parameters[@"stuff"][@"key2-form-name"], is(equalTo(@"value2")));
assertThat(parameters[@"stuff"][@"key1-form-name"], is(equalTo(@"value1")));
expect(parameters[@"stuff"][@"key2-form-name"]).to.equal(@"value2");
expect(parameters[@"stuff"][@"key1-form-name"]).to.equal(@"value1");
}
- (void)testShouldSerializeToManyRelationships
@@ -270,7 +270,7 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(string, is(equalTo(@"{\"hasMany\":[{\"date\":\"1970-01-01 00:00:00 +0000\"}],\"stringTest\":\"The string\"}")));
expect(string).to.equal(@"{\"hasMany\":[{\"date\":\"1970-01-01 00:00:00 +0000\"}],\"stringTest\":\"The string\"}");
}
- (void)testShouldSerializeAnNSNumberContainingABooleanToTrueFalseIfRequested
@@ -286,8 +286,8 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"{\"boolean-value\":true}")));
expect(error).to.beNil();
expect(string).to.equal(@"{\"boolean-value\":true}");
}
- (void)testShouldSerializeANSOrderedSetToJSON
@@ -305,8 +305,36 @@
NSData *data = [RKMIMETypeSerialization dataFromObject:parameters MIMEType:RKMIMETypeJSON error:&error];
NSString *string = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
assertThat(error, is(nilValue()));
assertThat(string, is(equalTo(@"{\"key1-form-name\":\"value1\",\"set-form-name\":[\"setElementOne\",\"setElementTwo\",\"setElementThree\"]}")));
expect(error).to.beNil();
expect(string).to.equal(@"{\"key1-form-name\":\"value1\",\"set-form-name\":[\"setElementOne\",\"setElementTwo\",\"setElementThree\"]}");
}
- (void)testParameterizationOfAttributesNestedByKeyPath
{
NSDictionary *object = @{ @"name" : @"Blake Watters", @"occupation" : @"Hacker" };
RKObjectMapping *mapping = [RKObjectMapping requestMapping];
[mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"user.name"]];
[mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"occupation" toKeyPath:@"user.job"]];
NSError *error = nil;
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[NSDictionary class] rootKeyPath:nil];
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:object requestDescriptor:requestDescriptor error:&error];
NSDictionary *expected = @{@"user": @{@"name": @"Blake Watters", @"job": @"Hacker"}};
expect(parameters).to.equal(expected);
}
- (void)testParameterizationOfAttributesDeeplyNestedByKeyPathToFormEncoded
{
NSDictionary *object = @{ @"name" : @"Blake Watters", @"occupation" : @"Hacker" };
RKObjectMapping *mapping = [RKObjectMapping requestMapping];
[mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"user.anotherKeyPath.name"]];
[mapping addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"occupation" toKeyPath:@"user.anotherKeyPath.another.job"]];
NSError *error = nil;
RKRequestDescriptor *requestDescriptor = [RKRequestDescriptor requestDescriptorWithMapping:mapping objectClass:[NSDictionary class] rootKeyPath:nil];
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:object requestDescriptor:requestDescriptor error:&error];
NSDictionary *expected = @{@"user": @{@"anotherKeyPath": @{@"name": @"Blake Watters", @"another": @{ @"job": @"Hacker"}}}};
expect(parameters).to.equal(expected);
}
@end