diff --git a/Code/ObjectMapping/RKObjectMappingOperation.m b/Code/ObjectMapping/RKObjectMappingOperation.m index b0aaab62..32670225 100644 --- a/Code/ObjectMapping/RKObjectMappingOperation.m +++ b/Code/ObjectMapping/RKObjectMappingOperation.m @@ -355,6 +355,22 @@ BOOL RKObjectIsValueEqualToValue(id sourceValue, id destinationValue) { continue; } + // Handle case where incoming content is collection represented by a dictionary + if (relationshipMapping.mapping.forceCollectionMapping) { + // If we have forced mapping of a dictionary, map each subdictionary + if ([value isKindOfClass:[NSDictionary class]]) { + RKLogDebug(@"Collection mapping forced for NSDictionary, mapping each key/value independently..."); + NSArray* objectsToMap = [NSMutableArray arrayWithCapacity:[value count]]; + for (id key in value) { + NSDictionary* dictionaryToMap = [NSDictionary dictionaryWithObject:[value valueForKey:key] forKey:key]; + [(NSMutableArray*)objectsToMap addObject:dictionaryToMap]; + } + value = objectsToMap; + } else { + RKLogWarning(@"Collection mapping forced but mappable objects is of type '%@' rather than NSDictionary", NSStringFromClass([value class])); + } + } + // Handle case where incoming content is a single object, but we want a collection Class relationshipType = [self.objectMapping classForProperty:relationshipMapping.destinationKeyPath]; BOOL mappingToCollection = (relationshipType && diff --git a/RestKit.xcodeproj/project.pbxproj b/RestKit.xcodeproj/project.pbxproj index 9821e696..19fbe234 100644 --- a/RestKit.xcodeproj/project.pbxproj +++ b/RestKit.xcodeproj/project.pbxproj @@ -62,7 +62,7 @@ 251939E913AABED40073A39B /* DynamicKeys.json in Resources */ = {isa = PBXBuildFile; fileRef = 251939E613AABED40073A39B /* DynamicKeys.json */; }; 251939EA13AABED40073A39B /* error.json in Resources */ = {isa = PBXBuildFile; fileRef = 251939E713AABED40073A39B /* error.json */; }; 251939EB13AABED40073A39B /* errors.json in Resources */ = {isa = PBXBuildFile; fileRef = 251939E813AABED40073A39B /* errors.json */; }; - 251939ED13ABA06D0073A39B /* DynamicKeysWithRelationship.json in Resources */ = {isa = PBXBuildFile; fileRef = 251939EC13ABA06D0073A39B /* DynamicKeysWithRelationship.json */; }; + 251939ED13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json in Resources */ = {isa = PBXBuildFile; fileRef = 251939EC13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json */; }; 251D14AC133597B800959061 /* RKManagedObjectLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 251D14AA133597B800959061 /* RKManagedObjectLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; 251D14AD133597B800959061 /* RKManagedObjectLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 251D14AB133597B800959061 /* RKManagedObjectLoader.m */; }; 2523363E11E7A1F00048F9B4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F6C3A9510FE7524008F47C5 /* UIKit.framework */; }; @@ -303,6 +303,7 @@ 3F71ED3413748536006281CA /* RKObjectMappingProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F71ED3213748536006281CA /* RKObjectMappingProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3F71ED3513748536006281CA /* RKObjectMappingProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 3F71ED3313748536006281CA /* RKObjectMappingProvider.m */; }; 3FD12C851379AD64008B996A /* RKRouter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FD12C841379AD64008B996A /* RKRouter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 40A0C79C14212B3B00D36DD2 /* DynamicKeysWithRelationship.json in Resources */ = {isa = PBXBuildFile; fileRef = 409341EC14211E7900EF4609 /* DynamicKeysWithRelationship.json */; }; 57D7EA2713D98672000E4E63 /* NSNumberCreator.m in Sources */ = {isa = PBXBuildFile; fileRef = 57D7EA2213D98672000E4E63 /* NSNumberCreator.m */; }; 57D7EA2813D98672000E4E63 /* UIConsoleLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 57D7EA2313D98672000E4E63 /* UIConsoleLog.m */; }; 57D7EA2913D98672000E4E63 /* UIExpectation.m in Sources */ = {isa = PBXBuildFile; fileRef = 57D7EA2413D98672000E4E63 /* UIExpectation.m */; }; @@ -465,7 +466,7 @@ 251939E613AABED40073A39B /* DynamicKeys.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DynamicKeys.json; sourceTree = ""; }; 251939E713AABED40073A39B /* error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = error.json; sourceTree = ""; }; 251939E813AABED40073A39B /* errors.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = errors.json; sourceTree = ""; }; - 251939EC13ABA06D0073A39B /* DynamicKeysWithRelationship.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DynamicKeysWithRelationship.json; sourceTree = ""; }; + 251939EC13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = DynamicKeysWithNestedRelationship.json; sourceTree = ""; }; 251D14AA133597B800959061 /* RKManagedObjectLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKManagedObjectLoader.h; sourceTree = ""; }; 251D14AB133597B800959061 /* RKManagedObjectLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKManagedObjectLoader.m; sourceTree = ""; }; 2523360511E79F090048F9B4 /* libRestKitThree20.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRestKitThree20.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -739,6 +740,7 @@ 3F71ED3213748536006281CA /* RKObjectMappingProvider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RKObjectMappingProvider.h; sourceTree = ""; }; 3F71ED3313748536006281CA /* RKObjectMappingProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RKObjectMappingProvider.m; sourceTree = ""; }; 3FD12C841379AD64008B996A /* RKRouter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RKRouter.h; path = Code/ObjectMapping/RKRouter.h; sourceTree = SOURCE_ROOT; }; + 409341EC14211E7900EF4609 /* DynamicKeysWithRelationship.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = DynamicKeysWithRelationship.json; sourceTree = ""; }; 57D7EA2213D98672000E4E63 /* NSNumberCreator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = NSNumberCreator.m; path = ../UISpecRunner/NSNumberCreator.m; sourceTree = ""; }; 57D7EA2313D98672000E4E63 /* UIConsoleLog.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = UIConsoleLog.m; path = ../UISpecRunner/UIConsoleLog.m; sourceTree = ""; }; 57D7EA2413D98672000E4E63 /* UIExpectation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = UIExpectation.m; path = ../UISpecRunner/UIExpectation.m; sourceTree = ""; }; @@ -1402,7 +1404,8 @@ children = ( 252CF8B713E255B90093BBD6 /* Dynamic */, 2515E7B913B36A7D00E013A4 /* ArrayOfResults.json */, - 251939EC13ABA06D0073A39B /* DynamicKeysWithRelationship.json */, + 409341EC14211E7900EF4609 /* DynamicKeysWithRelationship.json */, + 251939EC13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json */, 251939E613AABED40073A39B /* DynamicKeys.json */, 251939E713AABED40073A39B /* error.json */, 251939E813AABED40073A39B /* errors.json */, @@ -2023,6 +2026,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 40A0C79C14212B3B00D36DD2 /* DynamicKeysWithRelationship.json in Resources */, 25952EE1136F563E00D04F93 /* blake.png in Resources */, 25952EE2136F563E00D04F93 /* ComplexNestedUser.json in Resources */, 25952EE3136F563E00D04F93 /* Foursquare.json in Resources */, @@ -2036,7 +2040,7 @@ 251939E913AABED40073A39B /* DynamicKeys.json in Resources */, 251939EA13AABED40073A39B /* error.json in Resources */, 251939EB13AABED40073A39B /* errors.json in Resources */, - 251939ED13ABA06D0073A39B /* DynamicKeysWithRelationship.json in Resources */, + 251939ED13ABA06D0073A39B /* DynamicKeysWithNestedRelationship.json in Resources */, 25BA443513ABB34900ADC7D0 /* tab_data.xml in Resources */, 2515E7BA13B36A7D00E013A4 /* ArrayOfResults.json in Resources */, 252CF8B913E255D70093BBD6 /* boy.json in Resources */, diff --git a/Specs/Fixtures/JSON/DynamicKeysWithNestedRelationship.json b/Specs/Fixtures/JSON/DynamicKeysWithNestedRelationship.json new file mode 100644 index 00000000..69da4ab0 --- /dev/null +++ b/Specs/Fixtures/JSON/DynamicKeysWithNestedRelationship.json @@ -0,0 +1,33 @@ +{ +"groups":[ + { + "name":"restkit", + "users": { + "blake": { + "id": 31337, + "website": "http://restkit.org/" + }, + "rachit": { + "id": 7, + "website": "http://www.twotoasters.com/", + "address": { + "city": "New York", + "state": "New York" + } + } + } + }, + { + "name":"others", + "users":{ + "bjorn": { + "id": 10, + "address": { + "city": "Gothenburg", + "country": "Sweden" + } + } + } + } +] +} \ No newline at end of file diff --git a/Specs/ObjectMapping/RKObjectMappingNextGenSpec.m b/Specs/ObjectMapping/RKObjectMappingNextGenSpec.m index 40f710ce..b74e7de0 100644 --- a/Specs/ObjectMapping/RKObjectMappingNextGenSpec.m +++ b/Specs/ObjectMapping/RKObjectMappingNextGenSpec.m @@ -136,6 +136,68 @@ @end +@interface RKExampleGroupWithUserArray : NSObject { + NSString * _name; + NSArray* _users; +} + +@property (nonatomic, retain) NSString* name; +@property (nonatomic, retain) NSArray* users; + +@end + +@implementation RKExampleGroupWithUserArray + +@synthesize name = _name; +@synthesize users = _users; + ++ (RKExampleGroupWithUserArray*)group { + return [[self new] autorelease]; +} + +// isEqual: is consulted by the mapping operation +// to determine if assocation values should be set +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[RKExampleGroupWithUserArray class]]) { + return [[(RKExampleGroupWithUserArray*)object name] isEqualToString:self.name]; + } else { + return NO; + } +} + +@end + +@interface RKExampleGroupWithUserSet : NSObject { + NSString * _name; + NSSet* _users; +} + +@property (nonatomic, retain) NSString* name; +@property (nonatomic, retain) NSSet* users; + +@end + +@implementation RKExampleGroupWithUserSet + +@synthesize name = _name; +@synthesize users = _users; + ++ (RKExampleGroupWithUserSet*)group { + return [[self new] autorelease]; +} + +// isEqual: is consulted by the mapping operation +// to determine if assocation values should be set +- (BOOL)isEqual:(id)object { + if ([object isKindOfClass:[RKExampleGroupWithUserSet class]]) { + return [[(RKExampleGroupWithUserSet*)object name] isEqualToString:self.name]; + } else { + return NO; + } +} + +@end + //////////////////////////////////////////////////////////////////////////////// #pragma mark - @@ -454,6 +516,123 @@ [expectThat(user.address.city) should:be(@"New York")]; } +- (void)itShouldMapANestedArrayOfObjectsWithDynamicKeysAndArrayRelationships { + RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[RKExampleGroupWithUserArray class]]; + [mapping mapAttributes:@"name", nil]; + + + RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKExampleUser class]]; + userMapping.forceCollectionMapping = YES; + [userMapping mapKeyOfNestedDictionaryToAttribute:@"name"]; + [mapping mapKeyPath:@"users" toRelationship:@"users" withMapping:userMapping]; + + RKObjectMapping* addressMapping = [RKObjectMapping mappingForClass:[RKSpecAddress class]]; + [addressMapping mapAttributes: + @"city", @"city", + @"state", @"state", + @"country", @"country", + nil + ]; + [userMapping mapKeyPath:@"(name).address" toRelationship:@"address" withMapping:addressMapping]; + RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease]; + [provider setMapping:mapping forKeyPath:@"groups"]; + + id userInfo = RKSpecParseFixture(@"DynamicKeysWithNestedRelationship.json"); + RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:userInfo mappingProvider:provider]; + RKObjectMappingResult* result = [mapper performMapping]; + + NSArray* groups = [result asCollection]; + [expectThat([groups isKindOfClass:[NSArray class]]) should:be(YES)]; + [expectThat([groups count]) should:be(2)]; + + RKExampleGroupWithUserArray* group = [groups objectAtIndex:0]; + [expectThat([group isKindOfClass:[RKExampleGroupWithUserArray class]]) should:be(YES)]; + [expectThat(group.name) should:be(@"restkit")]; + NSArray * users = group.users; + [expectThat([users count]) should:be(2)]; + RKExampleUser* user = [users objectAtIndex:0]; + [expectThat([user isKindOfClass:[RKExampleUser class]]) should:be(YES)]; + [expectThat(user.name) should:be(@"blake")]; + user = [users objectAtIndex:1]; + [expectThat([user isKindOfClass:[RKExampleUser class]]) should:be(YES)]; + [expectThat(user.name) should:be(@"rachit")]; + [expectThat(user.address) shouldNot:be(nil)]; + [expectThat(user.address.city) should:be(@"New York")]; + + group = [groups objectAtIndex:1]; + [expectThat([group isKindOfClass:[RKExampleGroupWithUserArray class]]) should:be(YES)]; + [expectThat(group.name) should:be(@"others")]; + users = group.users; + [expectThat([users count]) should:be(1)]; + user = [users objectAtIndex:0]; + [expectThat([user isKindOfClass:[RKExampleUser class]]) should:be(YES)]; + [expectThat(user.name) should:be(@"bjorn")]; + [expectThat(user.address) shouldNot:be(nil)]; + [expectThat(user.address.city) should:be(@"Gothenburg")]; + [expectThat(user.address.country) should:be(@"Sweden")]; +} + +- (void)itShouldMapANestedArrayOfObjectsWithDynamicKeysAndSetRelationships { + RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[RKExampleGroupWithUserSet class]]; + [mapping mapAttributes:@"name", nil]; + + + RKObjectMapping* userMapping = [RKObjectMapping mappingForClass:[RKExampleUser class]]; + userMapping.forceCollectionMapping = YES; + [userMapping mapKeyOfNestedDictionaryToAttribute:@"name"]; + [mapping mapKeyPath:@"users" toRelationship:@"users" withMapping:userMapping]; + + RKObjectMapping* addressMapping = [RKObjectMapping mappingForClass:[RKSpecAddress class]]; + [addressMapping mapAttributes: + @"city", @"city", + @"state", @"state", + @"country", @"country", + nil + ]; + [userMapping mapKeyPath:@"(name).address" toRelationship:@"address" withMapping:addressMapping]; + RKObjectMappingProvider* provider = [[RKObjectMappingProvider new] autorelease]; + [provider setMapping:mapping forKeyPath:@"groups"]; + + id userInfo = RKSpecParseFixture(@"DynamicKeysWithNestedRelationship.json"); + RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:userInfo mappingProvider:provider]; + RKObjectMappingResult* result = [mapper performMapping]; + + NSArray* groups = [result asCollection]; + [expectThat([groups isKindOfClass:[NSArray class]]) should:be(YES)]; + [expectThat([groups count]) should:be(2)]; + + RKExampleGroupWithUserSet* group = [groups objectAtIndex:0]; + [expectThat([group isKindOfClass:[RKExampleGroupWithUserSet class]]) should:be(YES)]; + [expectThat(group.name) should:be(@"restkit")]; + + + NSSortDescriptor * sortByName =[[[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES] autorelease]; + NSArray * descriptors = [NSArray arrayWithObject:sortByName];; + NSArray * users = [group.users sortedArrayUsingDescriptors:descriptors]; + [expectThat([users count]) should:be(2)]; + RKExampleUser* user = [users objectAtIndex:0]; + [expectThat([user isKindOfClass:[RKExampleUser class]]) should:be(YES)]; + [expectThat(user.name) should:be(@"blake")]; + user = [users objectAtIndex:1]; + [expectThat([user isKindOfClass:[RKExampleUser class]]) should:be(YES)]; + [expectThat(user.name) should:be(@"rachit")]; + [expectThat(user.address) shouldNot:be(nil)]; + [expectThat(user.address.city) should:be(@"New York")]; + + group = [groups objectAtIndex:1]; + [expectThat([group isKindOfClass:[RKExampleGroupWithUserSet class]]) should:be(YES)]; + [expectThat(group.name) should:be(@"others")]; + users = [group.users sortedArrayUsingDescriptors:descriptors]; + [expectThat([users count]) should:be(1)]; + user = [users objectAtIndex:0]; + [expectThat([user isKindOfClass:[RKExampleUser class]]) should:be(YES)]; + [expectThat(user.name) should:be(@"bjorn")]; + [expectThat(user.address) shouldNot:be(nil)]; + [expectThat(user.address.city) should:be(@"Gothenburg")]; + [expectThat(user.address.country) should:be(@"Sweden")]; +} + + - (void)itShouldBeAbleToMapFromAUserObjectToADictionary { RKObjectMapping* mapping = [RKObjectMapping mappingForClass:[NSMutableDictionary class]]; RKObjectAttributeMapping* idMapping = [RKObjectAttributeMapping mappingFromKeyPath:@"userID" toKeyPath:@"id"];