diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bcf35c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +OCMapper.xcodeproj/project.xcworkspace/xcshareddata/OCMapper.xccheckout +OCMapper.xcodeproj/project.xcworkspace/xcuserdata/aryan.xcuserdatad/UserInterfaceState.xcuserstate +OCMapper.xcodeproj/project.xcworkspace/xcuserdata/aryan.xcuserdatad/WorkspaceSettings.xcsettings +OCMapper.xcodeproj/xcuserdata/aryan.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +OCMapper.xcodeproj/xcuserdata/aryan.xcuserdatad/xcschemes/OCMapper.xcscheme +OCMapper.xcodeproj/xcuserdata/aryan.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/OCMapper.xcodeproj/project.pbxproj b/OCMapper.xcodeproj/project.pbxproj index 8e4eb92..53ff065 100644 --- a/OCMapper.xcodeproj/project.pbxproj +++ b/OCMapper.xcodeproj/project.pbxproj @@ -145,7 +145,7 @@ 15B554AF171B7B3B0058E159 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 15B554B1171B7B3B0058E159 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 15B554B3171B7B3B0058E159 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; - 15B554CC171B7B3C0058E159 /* OCMapperTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCMapperTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + 15B554CC171B7B3C0058E159 /* OCMapperTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCMapperTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 15D5AB3F1869E90A007DB250 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; 15D5AB411869E910007DB250 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; 15E2B963171BEAEB00526C77 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -359,7 +359,7 @@ isa = PBXGroup; children = ( 15B554AB171B7B3B0058E159 /* OCMapper.app */, - 15B554CC171B7B3C0058E159 /* OCMapperTests.octest */, + 15B554CC171B7B3C0058E159 /* OCMapperTests.xctest */, ); name = Products; sourceTree = ""; @@ -489,7 +489,7 @@ ); name = OCMapperTests; productName = ObjectMapperTests; - productReference = 15B554CC171B7B3C0058E159 /* OCMapperTests.octest */; + productReference = 15B554CC171B7B3C0058E159 /* OCMapperTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ diff --git a/OCMapper.xcodeproj/xcshareddata/xcbaselines/15B554CB171B7B3C0058E159.xcbaseline/4A5EECE7-F017-42D5-8C4C-D8A64184E966.plist b/OCMapper.xcodeproj/xcshareddata/xcbaselines/15B554CB171B7B3C0058E159.xcbaseline/4A5EECE7-F017-42D5-8C4C-D8A64184E966.plist new file mode 100644 index 0000000..9a8cf0c --- /dev/null +++ b/OCMapper.xcodeproj/xcshareddata/xcbaselines/15B554CB171B7B3C0058E159.xcbaseline/4A5EECE7-F017-42D5-8C4C-D8A64184E966.plist @@ -0,0 +1,22 @@ + + + + + classNames + + ObjectMapperTests + + testPerformance + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.01 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/OCMapper.xcodeproj/xcshareddata/xcbaselines/15B554CB171B7B3C0058E159.xcbaseline/Info.plist b/OCMapper.xcodeproj/xcshareddata/xcbaselines/15B554CB171B7B3C0058E159.xcbaseline/Info.plist new file mode 100644 index 0000000..d06cb08 --- /dev/null +++ b/OCMapper.xcodeproj/xcshareddata/xcbaselines/15B554CB171B7B3C0058E159.xcbaseline/Info.plist @@ -0,0 +1,40 @@ + + + + + runDestinationsByUUID + + 4A5EECE7-F017-42D5-8C4C-D8A64184E966 + + localComputer + + busSpeedInMHz + 100 + cpuCount + 1 + cpuKind + Intel Core i7 + cpuSpeedInMHz + 2300 + logicalCPUCoresPerPackage + 8 + modelCode + MacBookPro10,1 + physicalCPUCoresPerPackage + 4 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + targetDevice + + modelCode + iPhone7,2 + platformIdentifier + com.apple.platform.iphonesimulator + + + + + diff --git a/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.h b/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.h index 53f0729..6179e6d 100644 --- a/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.h +++ b/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.h @@ -32,6 +32,7 @@ @interface InCodeMappingProvider : NSObject - (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey withObjectType:(Class)objectType forClass:(Class)class; +- (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey forClass:(Class)class withTransformer:(MappingTransformer)transformer; - (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey forClass:(Class)class; - (void)setDateFormatter:(NSDateFormatter *)dateFormatter forProperty:(NSString *)property andClass:(Class)class; diff --git a/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.m b/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.m index 59f32aa..222b677 100644 --- a/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.m +++ b/OCMapper/Source/Mapping Provider/In Code Mapping/InCodeMappingProvider.m @@ -53,22 +53,29 @@ - (id)init #pragma mark - Public Methods - -- (void)mapFromDictionaryKey:(NSString *)source toPropertyKey:(NSString *)propertyKey withObjectType:(Class)objectType forClass:(Class)class +- (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey withObjectType:(Class)objectType forClass:(Class)class { - ObjectMappingInfo *info = [[ObjectMappingInfo alloc] initWithDictionaryKey:source propertyKey:propertyKey andObjectType:objectType]; - NSString *key = [self uniqueKeyForClass:class andKey:source]; + ObjectMappingInfo *info = [[ObjectMappingInfo alloc] initWithDictionaryKey:dictionaryKey propertyKey:propertyKey andObjectType:objectType]; + NSString *key = [self uniqueKeyForClass:class andKey:dictionaryKey]; [self.mappingDictionary setObject:info forKey:key]; } -- (void)setDateFormatter:(NSDateFormatter *)dateFormatter forProperty:(NSString *)property andClass:(Class)class +- (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey forClass:(Class)class { - NSString *key = [self uniqueKeyForClass:class andKey:property]; - [self.dateFormatterDictionary setObject:dateFormatter forKey:key]; + [self mapFromDictionaryKey:dictionaryKey toPropertyKey:propertyKey withObjectType:nil forClass:class]; } -- (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey forClass:(Class)class +- (void)mapFromDictionaryKey:(NSString *)dictionaryKey toPropertyKey:(NSString *)propertyKey forClass:(Class)class withTransformer:(MappingTransformer)transformer { - [self mapFromDictionaryKey:dictionaryKey toPropertyKey:propertyKey withObjectType:nil forClass:class]; + ObjectMappingInfo *info = [[ObjectMappingInfo alloc] initWithDictionaryKey:dictionaryKey propertyKey:propertyKey andTransformer:transformer]; + NSString *key = [self uniqueKeyForClass:class andKey:dictionaryKey]; + [self.mappingDictionary setObject:info forKey:key]; +} + +- (void)setDateFormatter:(NSDateFormatter *)dateFormatter forProperty:(NSString *)property andClass:(Class)class +{ + NSString *key = [self uniqueKeyForClass:class andKey:property]; + [self.dateFormatterDictionary setObject:dateFormatter forKey:key]; } #pragma mark - public Methods - diff --git a/OCMapper/Source/ObjectMapper.m b/OCMapper/Source/ObjectMapper.m index 0598fe5..0e5b963 100644 --- a/OCMapper/Source/ObjectMapper.m +++ b/OCMapper/Source/ObjectMapper.m @@ -65,9 +65,9 @@ - (id)init if (self = [super init]) { [self populateClassNamesFromMainBundle]; - - self.mappedClassNames = [NSMutableDictionary dictionary]; - self.mappedPropertyNames = [NSMutableDictionary dictionary]; + + self.mappedClassNames = [NSMutableDictionary dictionary]; + self.mappedPropertyNames = [NSMutableDictionary dictionary]; } return self; @@ -184,7 +184,14 @@ - (NSDictionary *)processDictionaryFromObject:(NSObject *)object { if (class == [NSDate class]) { - propertyValue = [propertyValue description]; + if (self.defaultDateFormatter) + { + propertyValue = [self.defaultDateFormatter stringFromDate:propertyValue]; + } + else + { + propertyValue = [propertyValue description]; + } } else if ([propertyValue isKindOfClass:[NSArray class]] || [propertyValue isKindOfClass:[NSSet class]]) { @@ -195,7 +202,7 @@ - (NSDictionary *)processDictionaryFromObject:(NSObject *)object if (propertyValue) [props setObject:propertyValue forKey:propertyName]; } } - + free(properties); currentClass = class_getSuperclass(currentClass); } @@ -252,6 +259,7 @@ - (id)processDictionary:(NSDictionary *)source forClass:(Class)class ObjectMappingInfo *mappingInfo = [self.mappingProvider mappingInfoForClass:class andDictionaryKey:key]; id value = [normalizedSource objectForKey:(NSString *)key]; NSString *propertyName; + MappingTransformer mappingTransformer; Class objectType; id nestedObject; @@ -259,11 +267,12 @@ - (id)processDictionary:(NSDictionary *)source forClass:(Class)class { propertyName = [self.instanceProvider propertyNameForObject:object byCaseInsensitivePropertyName:mappingInfo.propertyKey]; objectType = mappingInfo.objectType; + mappingTransformer = mappingInfo.transformer; } else { propertyName = [self.instanceProvider propertyNameForObject:object byCaseInsensitivePropertyName:key]; - + if (propertyName && ([value isKindOfClass:[NSDictionary class]] || [value isKindOfClass:[NSArray class]])) { if ([value isKindOfClass:[NSDictionary class]]) @@ -273,18 +282,21 @@ - (id)processDictionary:(NSDictionary *)source forClass:(Class)class ,[self typeForProperty:propertyName andClass:class]]); } - if (!objectType) - { - objectType = [self classFromString:key]; - } - } - } - + if (!objectType) + { + objectType = [self classFromString:key]; + } + } + } + if (class && object && propertyName && [object respondsToSelector:NSSelectorFromString(propertyName)]) { ILog(@"Mapping key(%@) to property(%@) from data(%@)", key, propertyName, [value class]); - if ([value isKindOfClass:[NSDictionary class]]) + if (mappingTransformer) { + nestedObject = mappingTransformer(value, source); + } + else if ([value isKindOfClass:[NSDictionary class]]) { nestedObject = [self processDictionary:value forClass:objectType]; } @@ -349,67 +361,67 @@ - (id)processArray:(NSArray *)value forClass:(Class)class if (nestedObject) [collection addObject:nestedObject]; } - + return collection; } - (Class)classFromString:(NSString *)className { - Class result; - - if ([self.mappedClassNames objectForKey:className]) - { - result = NSClassFromString([self.mappedClassNames objectForKey:className]); - - if (result) - return result; - } - - __weak typeof(self) weakSelf = self; - - Class (^testClassName)(NSString *) = ^(NSString *classNameToTest) { - Class clazz = NSClassFromString(classNameToTest); - - if (clazz) - { - [weakSelf.mappedClassNames setObject:classNameToTest forKey:className]; - } - - return clazz; - }; - - NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - - NSString *predictedClassName = className; - if (testClassName(predictedClassName)) { return testClassName(predictedClassName); } - - predictedClassName = [NSString stringWithFormat:@"%@.%@", appName ,className.capitalizedString]; - if (testClassName(predictedClassName)) { return testClassName(predictedClassName); } - - NSString *classNameLowerCase = [className lowercaseString]; - - for (NSString *bundleClassName in self.classNamesInMainBundle) - { - @autoreleasepool - { - NSString *bundleClassNameLowerCase = [bundleClassName lowercaseString]; - NSString *appNameLowerCase = appName.lowercaseString; - - if ([bundleClassNameLowerCase isEqual:classNameLowerCase] || - [bundleClassNameLowerCase isEqual:[NSString stringWithFormat:@"%@.%@", appNameLowerCase, classNameLowerCase]] || - [[NSString stringWithFormat:@"%@s", bundleClassNameLowerCase] isEqual:classNameLowerCase] || - [[NSString stringWithFormat:@"%@s", bundleClassNameLowerCase] isEqual:[NSString stringWithFormat:@"%@.%@", appNameLowerCase, classNameLowerCase]] || - [[NSString stringWithFormat:@"%@es", bundleClassNameLowerCase] isEqual:classNameLowerCase] || - [[NSString stringWithFormat:@"%@es", bundleClassNameLowerCase] isEqual:[NSString stringWithFormat:@"%@.%@", appNameLowerCase, classNameLowerCase]]) - { - result = NSClassFromString(bundleClassName); - [self.mappedClassNames setObject:bundleClassName forKey:className]; - break; - } - } - } - - return result; + Class result; + + if ([self.mappedClassNames objectForKey:className]) + { + result = NSClassFromString([self.mappedClassNames objectForKey:className]); + + if (result) + return result; + } + + __weak typeof(self) weakSelf = self; + + Class (^testClassName)(NSString *) = ^(NSString *classNameToTest) { + Class clazz = NSClassFromString(classNameToTest); + + if (clazz) + { + [weakSelf.mappedClassNames setObject:classNameToTest forKey:className]; + } + + return clazz; + }; + + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + + NSString *predictedClassName = className; + if (testClassName(predictedClassName)) { return testClassName(predictedClassName); } + + predictedClassName = [NSString stringWithFormat:@"%@.%@", appName ,className.capitalizedString]; + if (testClassName(predictedClassName)) { return testClassName(predictedClassName); } + + NSString *classNameLowerCase = [className lowercaseString]; + + for (NSString *bundleClassName in self.classNamesInMainBundle) + { + @autoreleasepool + { + NSString *bundleClassNameLowerCase = [bundleClassName lowercaseString]; + NSString *appNameLowerCase = appName.lowercaseString; + + if ([bundleClassNameLowerCase isEqual:classNameLowerCase] || + [bundleClassNameLowerCase isEqual:[NSString stringWithFormat:@"%@.%@", appNameLowerCase, classNameLowerCase]] || + [[NSString stringWithFormat:@"%@s", bundleClassNameLowerCase] isEqual:classNameLowerCase] || + [[NSString stringWithFormat:@"%@s", bundleClassNameLowerCase] isEqual:[NSString stringWithFormat:@"%@.%@", appNameLowerCase, classNameLowerCase]] || + [[NSString stringWithFormat:@"%@es", bundleClassNameLowerCase] isEqual:classNameLowerCase] || + [[NSString stringWithFormat:@"%@es", bundleClassNameLowerCase] isEqual:[NSString stringWithFormat:@"%@.%@", appNameLowerCase, classNameLowerCase]]) + { + result = NSClassFromString(bundleClassName); + [self.mappedClassNames setObject:bundleClassName forKey:className]; + break; + } + } + } + + return result; } - (NSDate *)dateFromString:(NSString *)string forProperty:(NSString *)property andClass:(Class)class @@ -485,25 +497,25 @@ - (NSMutableArray *)commonDateFormaters - (NSString *)typeForProperty:(NSString *)property andClass:(Class)class { - NSString *key = [NSString stringWithFormat:@"%@.%@", NSStringFromClass(class), property]; - - if (self.mappedPropertyNames[key]) { - return self.mappedPropertyNames[key]; - } - - const char *type = property_getAttributes(class_getProperty(class, [property UTF8String])); - NSString *typeString = [NSString stringWithUTF8String:type]; - NSArray *attributes = [typeString componentsSeparatedByString:@","]; - NSString *typeAttribute = [attributes objectAtIndex:0]; - NSString *className = [[[typeAttribute substringFromIndex:1] - stringByReplacingOccurrencesOfString:@"@" withString:@""] - stringByReplacingOccurrencesOfString:@"\"" withString:@""]; - - if (className) { - self.mappedPropertyNames[key] = className; - } - - return className; + NSString *key = [NSString stringWithFormat:@"%@.%@", NSStringFromClass(class), property]; + + if (self.mappedPropertyNames[key]) { + return self.mappedPropertyNames[key]; + } + + const char *type = property_getAttributes(class_getProperty(class, [property UTF8String])); + NSString *typeString = [NSString stringWithUTF8String:type]; + NSArray *attributes = [typeString componentsSeparatedByString:@","]; + NSString *typeAttribute = [attributes objectAtIndex:0]; + NSString *className = [[[typeAttribute substringFromIndex:1] + stringByReplacingOccurrencesOfString:@"@" withString:@""] + stringByReplacingOccurrencesOfString:@"\"" withString:@""]; + + if (className) { + self.mappedPropertyNames[key] = className; + } + + return className; } @end diff --git a/OCMapper/Source/ObjectMappingInfo.h b/OCMapper/Source/ObjectMappingInfo.h index cf0d67c..029f5cc 100644 --- a/OCMapper/Source/ObjectMappingInfo.h +++ b/OCMapper/Source/ObjectMappingInfo.h @@ -27,12 +27,16 @@ #import +typedef id (^MappingTransformer)(id currentNode, id parentNode); + @interface ObjectMappingInfo : NSObject @property (nonatomic, copy) NSString *dictionaryKey; @property (nonatomic, copy) NSString *propertyKey; +@property (nonatomic, copy) MappingTransformer transformer; @property (nonatomic, assign) Class objectType; - (id)initWithDictionaryKey:(NSString *)aDictionaryKey propertyKey:(NSString *)aPropertyKey andObjectType:(Class)anObjectType; +- (id)initWithDictionaryKey:(NSString *)aDictionaryKey propertyKey:(NSString *)aPropertyKey andTransformer:(MappingTransformer)transformer; @end diff --git a/OCMapper/Source/ObjectMappingInfo.m b/OCMapper/Source/ObjectMappingInfo.m index 46dc354..a6ff9f9 100644 --- a/OCMapper/Source/ObjectMappingInfo.m +++ b/OCMapper/Source/ObjectMappingInfo.m @@ -46,4 +46,16 @@ - (id)initWithDictionaryKey:(NSString *)aDictionaryKey propertyKey:(NSString *)a return self; } +- (id)initWithDictionaryKey:(NSString *)aDictionaryKey propertyKey:(NSString *)aPropertyKey andTransformer:(MappingTransformer)transformer +{ + if (self = [super init]) + { + self.dictionaryKey = aDictionaryKey; + self.propertyKey = aPropertyKey; + self.transformer = transformer; + } + + return self; +} + @end diff --git a/OCMapperTests/ObjectMapperTests.m b/OCMapperTests/ObjectMapperTests.m index e3c0cca..5a637e4 100644 --- a/OCMapperTests/ObjectMapperTests.m +++ b/OCMapperTests/ObjectMapperTests.m @@ -40,7 +40,7 @@ @implementation ObjectMapperTests - (void)setUp { - [super setUp]; + [super setUp]; self.mappingProvider = [[InCodeMappingProvider alloc] init]; self.instanceProvider = [[ObjectInstanceProvider alloc] init]; @@ -55,7 +55,7 @@ - (void)tearDown self.mapper = nil; self.mappingProvider = nil; - [super tearDown]; + [super tearDown]; } #pragma mark - Tests - @@ -177,9 +177,9 @@ - (void)testAutomaticDMappingWithArrayOnRootLevel NSArray *users = [self.mapper objectFromSource:@[user1Dictionary, user2Dictionary] toInstanceOfClass:[User class]]; XCTAssertTrue(users.count == 2, @"Did not populate correct number of items"); XCTAssertTrue([[[users objectAtIndex:0] firstName] isEqual: - [user1Dictionary objectForKey:@"firstName"]], @"Did not populate correct attributes"); + [user1Dictionary objectForKey:@"firstName"]], @"Did not populate correct attributes"); XCTAssertTrue([[[users objectAtIndex:1] firstName] isEqual: - [user2Dictionary objectForKey:@"firstName"]], @"Did not populate correct attributes"); + [user2Dictionary objectForKey:@"firstName"]], @"Did not populate correct attributes"); } - (void)testCustomDateConversion @@ -239,8 +239,8 @@ - (void)testPerformance [usersDictionary addObject:userDictionary]; } - [self.mapper objectFromSource:usersDictionary toInstanceOfClass:[User class]]; - }]; + [self.mapper objectFromSource:usersDictionary toInstanceOfClass:[User class]]; + }]; } - (void)testDictionaryFromFlatObject @@ -316,7 +316,7 @@ - (void)testMappingshouldNotBeCaseSensitiveForPropertyNameWithCustomMapping NSString *firstNameProperty = @"FiRsTNaMe"; NSString *firstNameKey = @"first_name"; NSString *firstName = @"Aryan"; - + NSMutableDictionary *userDictionary = [NSMutableDictionary dictionary]; [userDictionary setObject:firstName forKey:firstNameKey]; @@ -359,7 +359,7 @@ - (void)testPlistMapping } - (void)testFlatDataToComplexObjectConversion -{ +{ NSMutableDictionary *userDictionary = [NSMutableDictionary dictionary]; [userDictionary setObject:@"Aryan" forKey:@"firstName"]; [userDictionary setObject:@"San Diego" forKey:@"city"]; @@ -403,4 +403,22 @@ - (void)testShouldPopulateDictionaryWithPropertyInSuperClass XCTAssertTrue([user.power isEqual:[dictionary objectForKey:@"power"]], @"Did Not populate dictionary properly"); } +- (void)testShouldTransformDataCorrectly { + NSMutableDictionary *userDictionary = [NSMutableDictionary dictionary]; + [userDictionary setObject:@"Aryan" forKey:@"firstName"]; + [userDictionary setObject:@"San Diego" forKey:@"address"]; + + Address *address = [[Address alloc] init]; + + [self.mappingProvider mapFromDictionaryKey:@"address" toPropertyKey:@"address" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) { + + address.city = currentNode; + return address; + }]; + + User *user = [self.mapper objectFromSource:userDictionary toInstanceOfClass:[User class]]; + XCTAssertTrue(user.address == address); + XCTAssertTrue([user.address.city isEqualToString:@"San Diego"]); +} + @end