├── CHANGELOG.md ├── CTXPropertyMapper.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── CTXPropertyMapperTests ├── User.m ├── TestData.json ├── User.h ├── Info.plist ├── CTXMultiMappingTest.m └── CTXPropertyMapperTests.m ├── CTXPropertyMapper ├── CTXPropertyMapperModelFactoryProtocol.h ├── CTXPropertyMapperErrors.h ├── Info.plist ├── CTXPropertyMapperErrors.m ├── CTXPropertyDescriptor+Validators.h ├── CTXPropertyMapper.h ├── CTXPropertyDescriptor.h ├── CTXPropertyDescriptor+Validators.m ├── CTXPropertyDescriptor.m └── CTXPropertyMapper.m ├── .gitignore ├── CTXPropertyMapper.podspec ├── LICENSE ├── CONTRIBUTING.md └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CTXPropertyMapper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CTXPropertyMapperTests/User.m: -------------------------------------------------------------------------------- 1 | // 2 | // User.m 3 | // OOGUiCommon 4 | // 5 | // Created by David Carvalho on 13/02/2015. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | #import "User.h" 10 | 11 | @implementation User 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyMapperModelFactoryProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyMapperModelFactoryProtocol.h 3 | // Pods 4 | // 5 | // Created by Mario on 26/01/2015. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | @protocol CTXPropertyMapperModelFactoryProtocol 10 | 11 | - (id)instanceForClass:(Class)class withDictionary:(NSDictionary *)dictionary; 12 | 13 | @end -------------------------------------------------------------------------------- /CTXPropertyMapperTests/TestData.json: -------------------------------------------------------------------------------- 1 | { 2 | "avatarURL": "http://server.com/avatarurlpath", 3 | "firstName": "Jon", 4 | "lastName": "Snow", 5 | "origin": "Winterfell, The North, Westeros", 6 | "quote":"you know nothing Jon Snow (Ygritte)", 7 | "status":{ 8 | "alive":true 9 | }, 10 | "job": { 11 | "title":"The bastard of Winterfell", 12 | "sector":"Castle Black", 13 | "hours":"Full Time" 14 | } 15 | } -------------------------------------------------------------------------------- /CTXPropertyMapperTests/User.h: -------------------------------------------------------------------------------- 1 | // 2 | // User.h 3 | // OOGUiCommon 4 | // 5 | // Created by David Carvalho on 13/02/2015. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface User : NSObject 12 | 13 | @property (nonatomic, strong) NSString *firstNameDifferent; 14 | @property (nonatomic, strong) NSString *lastName; 15 | @property (nonatomic, assign) BOOL active; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyMapperErrors.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyMapperErrors.h 3 | // Pods 4 | // 5 | // Created by Mario on 15/12/2014. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString *const kCTXPropertyMapperErrorDomain; 12 | 13 | typedef NS_ENUM(NSUInteger, CTXPropertyMapperErrorCode) { 14 | CTXPropertyMapperErrorCodeUnknownProperty = 60520, 15 | CTXPropertyMapperErrorCodeInvalidMapperFormat = 60530, 16 | CTXPropertyMapperErrorCodeMapperDidNotFound = 60540, 17 | CTXPropertyMapperErrorCodeValidationFailed = 60550, 18 | 19 | }; 20 | 21 | extern NSString *CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCode code); 22 | -------------------------------------------------------------------------------- /CTXPropertyMapper.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | 3 | spec.name = 'CTXPropertyMapper' 4 | spec.version = '1.3.0' 5 | spec.summary = 'Simple and highly extensible two ways property mapper' 6 | spec.homepage = "https://github.com/ef-ctx/CTXPropertyMapper" 7 | 8 | spec.license = { :type => 'MIT', :file => 'LICENSE' } 9 | 10 | spec.authors = { 11 | "Mário Araújo" => "mario.araujo@ef.com", 12 | "Stefan Ceriu" => "stefan.ceriu@ef.com" 13 | } 14 | 15 | spec.platform = :ios 16 | spec.ios.deployment_target = '6.0' 17 | spec.requires_arc = true 18 | 19 | spec.source = { :git => 'https://github.com/ef-ctx/CTXPropertyMapper.git', :tag => spec.version.to_s } 20 | spec.source_files = 'CTXPropertyMapper/*.{h,m}' 21 | 22 | end 23 | -------------------------------------------------------------------------------- /CTXPropertyMapperTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /CTXPropertyMapper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyMapperErrors.m: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyMapperErrors.m 3 | // Pods 4 | // 5 | // Created by Mario on 15/12/2014. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | #import "CTXPropertyMapperErrors.h" 10 | 11 | NSString *const kCTXPropertyMapperErrorDomain = @"com.ef.ctx.property-mapper"; 12 | 13 | extern NSString *CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCode code) 14 | { 15 | static NSDictionary *descriptions = nil; 16 | static dispatch_once_t onceToken = 0; 17 | dispatch_once(&onceToken, ^{ 18 | descriptions = @{ 19 | @(CTXPropertyMapperErrorCodeUnknownProperty) : @"Property Mapper contains a not found property [%@] on class [%@]", 20 | @(CTXPropertyMapperErrorCodeInvalidMapperFormat) : @"Property Mapper is invalid.", 21 | @(CTXPropertyMapperErrorCodeMapperDidNotFound) : @"Property Mapper for class [%@] is not found", 22 | @(CTXPropertyMapperErrorCodeValidationFailed) : @"[%@] validation failed on [%@]" 23 | }; 24 | }); 25 | return descriptions[@(code)]; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) EF Education First Group. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /CTXPropertyMapperTests/CTXMultiMappingTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CTXMultiMappingTest.m 3 | // CTXPropertyMapper 4 | // 5 | // Created by David Carvalho on 14/02/2015. 6 | // Copyright (c) 2015 EF. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CTXPropertyMapper.h" 11 | #import "User.h" 12 | 13 | @interface CTXMultiMappingTest : XCTestCase 14 | 15 | @end 16 | 17 | @implementation CTXMultiMappingTest 18 | 19 | - (void)testNewParser 20 | { 21 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 22 | 23 | NSDictionary *mappings = [CTXPropertyMapper generateMappingsFromClass:[User class]]; 24 | [mapper addMappings:mappings 25 | forClass:[User class]]; 26 | [mapper addMappings:@{@"firstName":CTXProperty(firstNameDifferent)} 27 | forClass:[User class]]; 28 | NSBundle *bundle = [NSBundle bundleForClass:self.class]; 29 | NSString *path = [bundle pathForResource:@"TestData" ofType:@"json"]; 30 | 31 | NSData *data = [NSData dataWithContentsOfFile:path]; 32 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data 33 | options:kNilOptions 34 | error:nil]; 35 | 36 | NSArray *errors = nil; 37 | User *user = [mapper createObjectWithClass:[User class] fromDictionary:json errors:&errors]; 38 | XCTAssert([user.firstNameDifferent isEqualToString:@"Jon"]); 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyDescriptor+Validators.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyDescriptor+Validators.h 3 | // Pods 4 | // 5 | // Created by Mario on 15/12/2014. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | // code borrowed from project KZPropertyMapper 9 | // https://github.com/krzysztofzablocki/KZPropertyMapper 10 | 11 | #import 12 | 13 | #import "CTXPropertyDescriptor.h" 14 | 15 | @interface CTXPropertyDescriptor(Validators) 16 | 17 | #pragma mark - Strings 18 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^isRequired)(); 19 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^lengthRange)(NSInteger min, NSInteger max); 20 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^matchesRegEx)(NSRegularExpression *regularExpression); 21 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^length)(NSUInteger length); 22 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^minLength)(NSInteger min); 23 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^maxLength)(NSInteger max); 24 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^oneOf)(NSArray *items); 25 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^equalTo)(NSString *value); 26 | 27 | 28 | #pragma mark - Numbers 29 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^min)(NSInteger min); 30 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^max)(NSInteger max); 31 | @property(nonatomic, copy, readonly) CTXPropertyDescriptor *(^range)(NSInteger min, NSInteger max); 32 | 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyMapper.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyMapper.h 3 | // CTXFramework 4 | // 5 | // Created by Mario on 09/12/2014. 6 | // Copyright (c) 2015 EF. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CTXPropertyMapperModelFactoryProtocol.h" 11 | #import "CTXPropertyDescriptor.h" 12 | #import "CTXPropertyDescriptor+Validators.h" 13 | #import "CTXPropertyMapperErrors.h" 14 | 15 | //! Project version number for CTXPropertyMapper. 16 | FOUNDATION_EXPORT double CTXPropertyMapperVersionNumber; 17 | 18 | //! Project version string for CTXPropertyMapper. 19 | FOUNDATION_EXPORT const unsigned char CTXPropertyMapperVersionString[]; 20 | 21 | typedef NS_ENUM(NSUInteger, CTXPropertyMapperExportOption) { 22 | CTXPropertyMapperExportOptionExcludeNullValue, 23 | CTXPropertyMapperExportOptionIncludeNullValue 24 | }; 25 | 26 | typedef void(^CTXFinalMappingDecoderBlock)(NSDictionary *input, id object); 27 | typedef void(^CTXFinalMappingEncoderBlock)(NSMutableDictionary *output, id object); 28 | 29 | typedef NS_ENUM(NSUInteger, CTXPropertyMapperFinalMappingDecoderOption) { 30 | CTXPropertyMapperFinalMappingDecoderOptionIncludeAllKeys, 31 | CTXPropertyMapperFinalMappingDecoderOptionExcludeAlreadyMappedKeys 32 | }; 33 | 34 | @interface CTXPropertyMapper : NSObject 35 | 36 | - (instancetype)initWithModelFactory:(id)modelFactory; 37 | 38 | 39 | - (BOOL)addMappings:(NSDictionary *)mappings forClass:(Class)clazz; 40 | - (BOOL)addMappings:(NSDictionary *)mappings forClass:(Class)clazz error:(NSError *__autoreleasing*)error; 41 | 42 | - (void)addMappingsFromPropertyMapper:(CTXPropertyMapper *)propertyMapper; 43 | 44 | 45 | - (BOOL)setMappings:(NSDictionary *)mappings forClass:(Class)clazz; 46 | - (BOOL)setMappings:(NSDictionary *)mappings forClass:(Class)clazz error:(NSError *__autoreleasing*)error; 47 | 48 | - (void)setFinalMappingEncoder:(CTXFinalMappingEncoderBlock)encoder forClass:(Class)clazz; 49 | - (void)setFinalMappingDecoder:(CTXFinalMappingDecoderBlock)decoder forClass:(Class)clazz withOption:(CTXPropertyMapperFinalMappingDecoderOption)option; 50 | 51 | - (BOOL)removeMappingsForClass:(Class)clazz; 52 | 53 | 54 | - (id)createObjectWithClass:(Class)clazz fromDictionary:(NSDictionary *)dictionary; 55 | - (id)createObjectWithClass:(Class)clazz fromDictionary:(NSDictionary *)dictionary errors:(NSArray *__autoreleasing*)errors; 56 | 57 | 58 | - (NSDictionary *)exportObject:(id)object; 59 | - (NSDictionary *)exportObject:(id)object withOptions:(enum CTXPropertyMapperExportOption)options; 60 | 61 | 62 | + (NSDictionary *)generateMappingsFromClass:(Class)clazz; 63 | + (NSDictionary *)generateMappingsWithKeys:(NSArray *)keys; 64 | 65 | @end -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyDescriptor.h: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyDescriptor.h 3 | // Pods 4 | // 5 | // Created by Mario on 15/12/2014. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define CTXProperty(property) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property)];}) 12 | #define CTXPropertyEncode(property) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property) mode:CTXPropertyMapperCodificationModeEncode];}) 13 | #define CTXPropertyDecode(property) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property) mode:CTXPropertyMapperCodificationModeDecode];}) 14 | #define CTXClass(property, clazz) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property) withClass:clazz];}) 15 | #define CTXClassEncode(property, clazz) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property) withClass:clazz mode:CTXPropertyMapperCodificationModeEncode];}) 16 | #define CTXClassDecode(property, clazz) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property) withClass:clazz mode:CTXPropertyMapperCodificationModeDecode];}) 17 | #define CTXBlock(property, encoder, decoder) ({[[CTXPropertyDescriptor alloc] initWithSelector:@selector(property) encondingBlock:encoder decodingBlock:decoder];}) 18 | #define CTXGenerationConsumerBlock(encoder, decoder) ({[[CTXPropertyDescriptor alloc] initWithEncodingGenerationBlock:encoder decodingConsumerBlock:decoder];}) 19 | 20 | typedef NS_ENUM(NSUInteger, CTXPropertyMapperCodificationMode) { 21 | CTXPropertyMapperCodificationModeEncode = 1, 22 | CTXPropertyMapperCodificationModeDecode = 2, 23 | CTXPropertyMapperCodificationModeEncodeAndDecode = 3//Encode + Decode to allow bitwise operation 24 | }; 25 | 26 | typedef NS_ENUM(NSUInteger, CTXPropertyDescriptorType) { 27 | CTXPropertyDescriptorTypeDirect, 28 | CTXPropertyDescriptorTypeClass, 29 | CTXPropertyDescriptorTypeSymmetricalBlock, 30 | CTXPropertyDescriptorTypeAsymmetricalBlock 31 | }; 32 | 33 | typedef id(^CTXValueTransformerBlock)(id input, NSString *propertyName); 34 | 35 | typedef id(^CTXValueGenerationBlock)(id object); 36 | typedef void(^CTXValueConsumerBlock)(id input, id object); 37 | 38 | @interface CTXPropertyDescriptor : NSObject 39 | @property (nonatomic, readonly) NSString *propertyName; 40 | @property (nonatomic, assign, readonly) Class propertyClass; 41 | @property (nonatomic, readonly) NSMutableArray *validationBlocks; 42 | @property (nonatomic, readonly) CTXValueTransformerBlock encodingBlock; 43 | @property (nonatomic, readonly) CTXValueTransformerBlock decodingBlock; 44 | @property (nonatomic, readonly) CTXValueGenerationBlock encodingGenerationBlock; 45 | @property (nonatomic, readonly) CTXValueConsumerBlock decodingConsumerBlock; 46 | @property (nonatomic, readonly) enum CTXPropertyMapperCodificationMode mode; 47 | @property (nonatomic, readonly) enum CTXPropertyDescriptorType type; 48 | 49 | - (instancetype)initWithSelector:(SEL)selector; 50 | - (instancetype)initWithSelector:(SEL)selector mode:(enum CTXPropertyMapperCodificationMode)mode; 51 | - (instancetype)initWithSelector:(SEL)selector withClass:(Class)clazz; 52 | - (instancetype)initWithSelector:(SEL)selector withClass:(Class)clazz mode:(enum CTXPropertyMapperCodificationMode)mode; 53 | - (instancetype)initWithSelector:(SEL)selector encondingBlock:(CTXValueTransformerBlock)encoder decodingBlock:(CTXValueTransformerBlock)decoder; 54 | - (instancetype)initWithEncodingGenerationBlock:(CTXValueGenerationBlock)encoder decodingConsumerBlock:(CTXValueConsumerBlock)decoder; 55 | 56 | - (void)addValidatorWithName:(NSString *)name validation:(BOOL (^)(id value))validator; 57 | - (NSArray *)validateValue:(id)value; 58 | @end 59 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyDescriptor+Validators.m: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyDescriptor+Validators.m 3 | // Pods 4 | // 5 | // Created by Mario on 15/12/2014. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | // code borrowed from project KZPropertyMapper 9 | // https://github.com/krzysztofzablocki/KZPropertyMapper 10 | 11 | #import 12 | #import "CTXPropertyDescriptor+Validators.h" 13 | 14 | @implementation CTXPropertyDescriptor (Validators) 15 | 16 | #pragma mark - Strings 17 | - (CTXPropertyDescriptor * (^)())isRequired 18 | { 19 | return ^() { 20 | [self addValidatorWithName:@"isRequired" validation:^(id value) { 21 | return YES; 22 | }]; 23 | return self; 24 | }; 25 | } 26 | 27 | - (CTXPropertyDescriptor * (^)(NSInteger, NSInteger))lengthRange 28 | { 29 | return ^(NSInteger min, NSInteger max) { 30 | NSInteger realMax = MAX(min, max); 31 | NSInteger realMin = MIN(min, max); 32 | [self addValidatorWithName:@"lengthRange" validation:^BOOL(NSString *value) { 33 | return value.length >= realMin && value.length <= realMax; 34 | }]; 35 | return self; 36 | }; 37 | } 38 | 39 | - (CTXPropertyDescriptor * (^)(NSRegularExpression *regEx))matchesRegEx 40 | { 41 | return ^(NSRegularExpression *regEx) { 42 | [self addValidatorWithName:@"matchesRegex" validation:^BOOL(NSString *value) { 43 | NSUInteger matches = [regEx numberOfMatchesInString:value options:0 range:NSMakeRange(0, value.length)]; 44 | return matches == 1; 45 | }]; 46 | return self; 47 | }; 48 | } 49 | 50 | - (CTXPropertyDescriptor * (^)(NSUInteger length))length 51 | { 52 | return ^(NSUInteger number) { 53 | [self addValidatorWithName:@"length" validation:^BOOL(NSString *value) { 54 | return value.length == number; 55 | }]; 56 | return self; 57 | }; 58 | } 59 | 60 | - (CTXPropertyDescriptor * (^)(NSInteger minLength))minLength 61 | { 62 | return ^(NSInteger number) { 63 | [self addValidatorWithName:@"minLength" validation:^BOOL(NSString *value) { 64 | return value.length >= number; 65 | }]; 66 | return self; 67 | }; 68 | } 69 | 70 | - (CTXPropertyDescriptor * (^)(NSInteger maxLength))maxLength 71 | { 72 | return ^(NSInteger number) { 73 | [self addValidatorWithName:@"maxLength" validation:^BOOL(NSString *value) { 74 | return value.length <= number; 75 | }]; 76 | return self; 77 | }; 78 | } 79 | 80 | - (CTXPropertyDescriptor * (^)(NSArray *))oneOf 81 | { 82 | return ^(NSArray *array) { 83 | [self addValidatorWithName:@"oneOf" validation:^BOOL(NSString *value) { 84 | return [array containsObject:value]; 85 | }]; 86 | return self; 87 | }; 88 | } 89 | 90 | - (CTXPropertyDescriptor * (^)(NSString *))equalTo 91 | { 92 | return ^(NSString *compare) { 93 | [self addValidatorWithName:@"equalTo" validation:^BOOL(NSString* value) { 94 | return [value isEqualToString:compare]; 95 | }]; 96 | return self; 97 | }; 98 | } 99 | 100 | #pragma mark - Numbers 101 | 102 | - (CTXPropertyDescriptor * (^)(NSInteger min))min 103 | { 104 | return ^(NSInteger min) { 105 | [self addValidatorWithName:@"min" validation:^BOOL(NSNumber *value) { 106 | return value.integerValue >= min; 107 | }]; 108 | return self; 109 | }; 110 | } 111 | 112 | - (CTXPropertyDescriptor * (^)(NSInteger max))max 113 | { 114 | return ^(NSInteger maxNumber) { 115 | [self addValidatorWithName:@"max" validation:^BOOL(NSNumber *value) { 116 | BOOL v = value.integerValue <= maxNumber; 117 | return v; 118 | }]; 119 | return self; 120 | }; 121 | } 122 | 123 | - (CTXPropertyDescriptor * (^)(NSInteger, NSInteger))range 124 | { 125 | return ^(NSInteger min, NSInteger max) { 126 | NSInteger realMax = MAX(min, max); 127 | NSInteger realMin = MIN(min, max); 128 | [self addValidatorWithName:@"range" validation:^BOOL(NSNumber *value) { 129 | return value.integerValue >= realMin && value.integerValue <= realMax; 130 | }]; 131 | return self; 132 | }; 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyDescriptor.m: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyDescriptor.m 3 | // Pods 4 | // 5 | // Created by Mario on 15/12/2014. 6 | // Copyright (c) 2015 EF Ltd. All rights reserved. 7 | // 8 | 9 | #import "CTXPropertyDescriptor.h" 10 | #import "CTXPropertyMapperErrors.h" 11 | 12 | @interface CTXPropertyDescriptor() 13 | @property (nonatomic, strong) NSString *propertyName; 14 | @property (nonatomic, assign) Class propertyClass; 15 | @property (nonatomic, strong) NSMutableArray *validationBlocks; 16 | @property (nonatomic, strong) CTXValueTransformerBlock encodingBlock; 17 | @property (nonatomic, strong) CTXValueTransformerBlock decodingBlock; 18 | 19 | @property (nonatomic, assign) enum CTXPropertyDescriptorType type; 20 | @property (nonatomic, assign, readwrite) enum CTXPropertyMapperCodificationMode mode; 21 | @end 22 | 23 | @implementation CTXPropertyDescriptor 24 | 25 | - (instancetype)init 26 | { 27 | [self doesNotRecognizeSelector:_cmd]; 28 | return nil; 29 | } 30 | 31 | - (instancetype)initWithSelector:(SEL)selector 32 | { 33 | return [self initWithSelector:selector mode:CTXPropertyMapperCodificationModeEncodeAndDecode]; 34 | } 35 | 36 | - (instancetype)initWithSelector:(SEL)selector mode:(enum CTXPropertyMapperCodificationMode)mode 37 | { 38 | if (self = [super init]) { 39 | _propertyName = NSStringFromSelector(selector); 40 | _type = CTXPropertyDescriptorTypeDirect; 41 | _mode = mode; 42 | } 43 | return self; 44 | } 45 | 46 | - (instancetype)initWithSelector:(SEL)selector withClass:(Class)clazz 47 | { 48 | return [self initWithSelector:selector withClass:clazz mode:CTXPropertyMapperCodificationModeEncodeAndDecode]; 49 | } 50 | 51 | - (instancetype)initWithSelector:(SEL)selector withClass:(Class)clazz mode:(enum CTXPropertyMapperCodificationMode)mode 52 | { 53 | if (self = [super init]) { 54 | _propertyName = NSStringFromSelector(selector); 55 | _propertyClass = clazz; 56 | _type = CTXPropertyDescriptorTypeClass; 57 | _mode = mode; 58 | } 59 | return self; 60 | } 61 | 62 | - (instancetype)initWithSelector:(SEL)selector encondingBlock:(CTXValueTransformerBlock)encoder decodingBlock:(CTXValueTransformerBlock)decoder 63 | { 64 | if (self = [super init]) { 65 | _propertyName = NSStringFromSelector(selector); 66 | _type = CTXPropertyDescriptorTypeSymmetricalBlock; 67 | _encodingBlock = encoder; 68 | _decodingBlock = decoder; 69 | 70 | if (encoder) { 71 | _mode += CTXPropertyMapperCodificationModeEncode; 72 | } 73 | 74 | if (decoder) { 75 | _mode += CTXPropertyMapperCodificationModeDecode; 76 | } 77 | 78 | if (_mode == 0) { 79 | NSAssert(YES, @"At least one codification block should be provided!"); 80 | } 81 | } 82 | return self; 83 | } 84 | 85 | - (instancetype)initWithEncodingGenerationBlock:(CTXValueGenerationBlock)encoder decodingConsumerBlock:(CTXValueConsumerBlock)decoder 86 | { 87 | if (self = [super init]) { 88 | _type = CTXPropertyDescriptorTypeAsymmetricalBlock; 89 | _encodingGenerationBlock = encoder; 90 | _decodingConsumerBlock = decoder; 91 | 92 | if (encoder) { 93 | _mode += CTXPropertyMapperCodificationModeEncode; 94 | } 95 | 96 | if (decoder) { 97 | _mode += CTXPropertyMapperCodificationModeDecode; 98 | } 99 | 100 | if (_mode == 0) { 101 | NSAssert(YES, @"At least one codification block should be provided!"); 102 | } 103 | } 104 | return self; 105 | } 106 | 107 | - (void)addValidationWithBlock:(NSError * (^)(NSString *name, NSString *))validationBlock 108 | { 109 | if (!self.validationBlocks) { 110 | self.validationBlocks = [NSMutableArray new]; 111 | } 112 | 113 | [self.validationBlocks addObject:validationBlock]; 114 | } 115 | 116 | - (void)addValidatorWithName:(NSString *)name validation:(BOOL (^)(id value))validator 117 | { 118 | [self addValidationWithBlock:^NSError *(NSString *value, NSString *propertyName) { 119 | BOOL validationResult = validator(value); 120 | if ([value isKindOfClass:NSNull.class] || !value || !validationResult) { 121 | NSString *description = [NSString stringWithFormat:CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCodeValidationFailed), name, propertyName]; 122 | 123 | NSError *error = [NSError errorWithDomain:kCTXPropertyMapperErrorDomain 124 | code:CTXPropertyMapperErrorCodeValidationFailed 125 | userInfo:@{NSLocalizedDescriptionKey : description}]; 126 | 127 | return error; 128 | } 129 | return nil; 130 | }]; 131 | } 132 | 133 | - (NSArray *)validateValue:(id)value 134 | { 135 | NSMutableArray *errors = [NSMutableArray new]; 136 | [self.validationBlocks enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 137 | NSError *(^validationBlock)(id, NSString *) = obj; 138 | NSError *error = validationBlock(value, self.propertyName); 139 | if (error) { 140 | [errors addObject:error]; 141 | } 142 | }]; 143 | return errors; 144 | } 145 | @end 146 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love for you to contribute to our source code and to make it even better than it is 4 | today! 5 | 6 | Here are the guidelines we'd like you to follow: 7 | 8 | - [Issues](#issues) 9 | - [Pull Requests](#pulls) 10 | - [Coding Rules](#rules) 11 | - [Commit Message Guidelines](#commit) 12 | 13 | ## Issues 14 | 15 | If you have questions about how to use this component, please open a [GitHub Issue](https://github.com/ef-ctx/CTXPropertyMapper/issues). 16 | 17 | If you find a bug in the source code or a mistake in the documentation, you can help us by 18 | submitting an issue to our [GitHub Issue](https://github.com/ef-ctx/CTXPropertyMapper/issues). Even better you can submit a Pull Request 19 | with a fix. 20 | 21 | Before you submit your issue search the archive, maybe your question was already answered. 22 | 23 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 24 | Help us to maximize the effort we can spend fixing issues and adding new 25 | features, by not reporting duplicate issues. Providing the following information will increase the 26 | chances of your issue being dealt with quickly: 27 | 28 | * **Overview of the Issue** - if an error is being thrown a formatted stack trace will be very helpful 29 | * **Motivation for or Use Case** - explain why this is a bug for you 30 | * **Version(s) Affected** - is it a regression? 31 | * **Systems Affected** - identify platform/os/browser where applicable 32 | * **Reproduce the Error** - if possible, provide a set of steps 33 | * **Related Issues** - has a similar issue been reported before? please link it 34 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 35 | causing the problem (line of code or commit) 36 | 37 | ## Pull Requests 38 | 39 | Before you submit your pull request consider the following guidelines: 40 | 41 | * Search [Pull Requests](https://github.com/ef-ctx/CTXPropertyMapper/pulls) for an open or closed Pull Request 42 | that relates to your submission. You don't want to duplicate effort. 43 | * Make your changes in a new git branch: 44 | 45 | ```shell 46 | git checkout -b fix-branch master 47 | ``` 48 | 49 | * Create your patch, **including appropriate test cases**. 50 | * Limit the changes to a well defined scope. 51 | * Avoid performing unrelated changes, even if minor (like fixing typos or code style in unrelated files). 52 | * Run the full test suites and ensure that all tests pass. 53 | * Follow the existing code style and guidelines where available. 54 | * Make sure you run existing beautifiers if available. 55 | * Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). 56 | 57 | ```shell 58 | git commit -a 59 | ``` 60 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 61 | 62 | * Build your changes locally to ensure all the tests pass: 63 | 64 | * Push your branch to GitHub: 65 | 66 | ```shell 67 | git push origin fix-branch 68 | ``` 69 | 70 | * In GitHub, send a pull request to `CTXPropertyMapper:master`. 71 | * If we suggest changes then: 72 | * Make the required updates. 73 | * Re-run the test suite to ensure tests are still passing. 74 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 75 | 76 | ```shell 77 | git rebase master -i 78 | git push origin fix-branch -f 79 | ``` 80 | 81 | That's it! Thank you for your contribution! 82 | 83 | ### After your pull request is merged 84 | 85 | After your pull request is merged, you can safely delete your branch and pull the changes 86 | from the main (upstream) repository: 87 | 88 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: 89 | 90 | ```shell 91 | git push origin --delete fix-branch 92 | ``` 93 | 94 | * Check out the master branch: 95 | 96 | ```shell 97 | git checkout master -f 98 | ``` 99 | 100 | * Delete the local branch: 101 | 102 | ```shell 103 | git branch -D fix-branch 104 | ``` 105 | 106 | * Update your master with the latest upstream version: 107 | 108 | ```shell 109 | git pull --ff upstream master 110 | ``` 111 | 112 | ## Coding Rules 113 | 114 | To ensure consistency throughout the source code, keep these rules in mind as you are working: 115 | 116 | * All features or bug fixes **must be tested** by one or more unit tests. 117 | * All public API methods **must be documented** in consistency with existing documentation. 118 | 119 | ## Git Commit Guidelines 120 | 121 | We have very precise rules over how our git commit messages can be formatted. This leads to **more 122 | readable messages** that are easy to follow when looking through the **project history**. But also, 123 | we use the git commit messages to **generate the CHANGELOG**. 124 | 125 | ### Commit Message Format 126 | 127 | Each commit message consists of a **header** and a **body**. The header has a special format that includes 128 | a **type**, a **scope** and a **subject**: 129 | 130 | ``` 131 | (): 132 | 133 | 134 | ``` 135 | 136 | Any line of the commit message cannot be longer 100 characters! This allows the message to be easier 137 | to read on github as well as in various git tools. 138 | 139 | ### Type 140 | Must be one of the following: 141 | 142 | * **feat**: A new feature 143 | * **fix**: A bug fix 144 | * **docs**: Documentation only changes 145 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing 146 | semi-colons, etc) 147 | * **refactor**: A code change that neither fixes a bug or adds a feature 148 | * **perf**: A code change that improves performance 149 | * **test**: Adding missing tests 150 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation 151 | generation 152 | 153 | ### Scope 154 | The scope could be anything specifying place of the commit change. For example `config`, 155 | `controller`, `filter`, etc... 156 | 157 | ### Subject 158 | The subject contains succinct description of the change: 159 | 160 | * use the imperative, present tense: "change" not "changed" nor "changes" 161 | * don't capitalize 162 | * no dot (.) at the end 163 | 164 | ### Body 165 | Provide more details about the commit in the message body, after the blank link: 166 | 167 | - include the motivation for the change and contrast this with previous behavior. 168 | - include information about **Breaking Changes** 169 | - reference existing Github issue(s) number via `GH-xxxx` instead of `#xxx`. 170 | 171 | Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". 172 | 173 | --- 174 | Adapted from https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTXPropertyMapper 2 | 3 | Simple property mapper that solves the most common parsing problems. 4 | 5 | - Data Validation 6 | - Type conversion 7 | - Handle optional properties 8 | - Simple to use and highly extensible 9 | 10 | ## Prerequisites 11 | 12 | CTXPropertyMapper takes advantage of recent Objective-C runtime additions, including ARC and blocks. It requires: 13 | 14 | - iOS 6 or later. 15 | - OS X 10.7 or later. 16 | 17 | ## Installing 18 | 19 | To install using CocoaPods add the following line to your project Podfile: 20 | 21 | ````ruby 22 | pod 'CTXPropertyMapper' 23 | ```` 24 | 25 | ## Example of usage 26 | 27 | Assuming the following model: 28 | 29 | ````objc 30 | @interface User : NSObject 31 | @property (nonatomic, strong) NSString *firstName; 32 | @property (nonatomic, strong) NSString *lastName; 33 | @property (nonatomic, assign) NSInteger age; 34 | @property (nonatomic, assign) BOOL active; 35 | @property (nonatomic, strong) Job *job; 36 | @property (nonatomic, strong) NSURL *profileURL; 37 | @end 38 | ```` 39 | 40 | and receiving the following json object: 41 | 42 | ````json 43 | { 44 | "avatarURL": "http://server.com/avatarurlpath", 45 | "firstName": "Jon", 46 | "lastName": "Snow", 47 | "origin": "Winterfell, The North, Westeros", 48 | "quote":"you know nothing Jon Snow (Ygritte)", 49 | "status":{ 50 | "alive":true 51 | }, 52 | "job": { 53 | "title":"The bastard of Winterfell", 54 | "sector":"Castle Black", 55 | "hours":"Full Time" 56 | } 57 | } 58 | ```` 59 | 60 | the property mapper can be configured like so: 61 | 62 | ````objc 63 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 64 | 65 | [mapper addMappings:@{@"firstName":CTXProperty(firstName), 66 | @"lastName":CTXProperty(lastName), 67 | @"status.alive":CTXProperty(active) 68 | } 69 | forClass:[User class]]; 70 | 71 | //Decoding 72 | NSArray *errors = nil; 73 | User *user = [mapper createObjectWithClass:[User class] fromDictionary:dictionary errors:&errors]; 74 | 75 | //Encoding 76 | NSDictionary *output = [mapper exportObject:user]; 77 | ```` 78 | 79 | ## Advanced usage 80 | 81 | CTXPropertyMapper is flexible enough to parse complex and nested objects. 82 | 83 | ````objc 84 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 85 | 86 | //URL 87 | CTXValueTransformerBlock decodeURL = ^id(NSString *input, NSString *propertyName){ 88 | return [NSURL URLWithString:input]; 89 | }; 90 | 91 | CTXValueTransformerBlock encodeURL = ^id(NSURL *input, NSString *propertyName){ 92 | return [input absoluteString]; 93 | }; 94 | 95 | //Origin 96 | CTXValueConsumerBlock decodeOrigin = ^void(NSString *input, User *user){ 97 | NSArray *originParts = [input componentsSeparatedByString:@","]; 98 | if (originParts.count == 3) { 99 | user.origin = originParts[0]; 100 | user.region = originParts[1]; 101 | user.continent = originParts[2]; 102 | } 103 | }; 104 | 105 | CTXValueGenerationBlock *encodeOrigin = ^id(id object){ 106 | return [[user.origin, user.region, user.continent] componentsJoinedByString:@","]; 107 | }; 108 | 109 | [mapper addMappings:@{@"title":CTXProperty(title), 110 | @"sector":CTXProperty(sector), 111 | @"hours":CTXProperty(hours), 112 | } 113 | forClass:[Job class]]; 114 | 115 | [mapper addMappings:@{@"firstName":CTXProperty(firstName), 116 | @"lastName":CTXProperty(lastName), 117 | @"job":CTXClass(job, [Job class]), 118 | @"avatarURL":CTXBlock(profileURL, encodeURL, decodeURL), 119 | @"origin":CTXGenerationConsumerBlock(encodeOrigin, decodeOrigin) 120 | } 121 | forClass:[User class]]; 122 | 123 | User *user = [mapper createObjectWithClass:[User class] fromDictionary:dictionary]; 124 | ```` 125 | 126 | ## Custom Factory 127 | 128 | CTXPropertyMapper uses the default object initializer internally, but some technologies like CoreData require a custom initializer. To support that you can use your own custom Factory implementing the protocol `CTXPropertyMapperModelFactoryProtocol`. The factory receives the class type and a dictionary for added flexibility, allowing the use of already created models, or fetching model instance from the local storage. 129 | 130 | ````objc 131 | @interface CoreDataModelFactory : NSObject 132 | - (instancetype)initWithContext:(NSManagedObjectContext *)context; 133 | @end 134 | 135 | @implementation CoreDataModelFactory 136 | - (id)instanceForClass:(Class)class withDictionary:(NSDictionary *)dictionary 137 | { 138 | NSEntityDescription *entity = [NSEntityDescription entityForName:[class description]] inManagedObjectContext:self.context]; 139 | return [[NSManagedObjec alloc] initWithEntity:entity insertIntoManagedObjectContext:self.context]; 140 | } 141 | @end 142 | ```` 143 | 144 | Now just create your instance of CTXPropertyMapper initializing with your custom model factory. 145 | 146 | ````objc 147 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] initWithModelFactory:[[CoreDataModelFactory alloc] init]]; 148 | ```` 149 | 150 | ## Final Encoders and Decoders 151 | 152 | CTXPropertyMapper is very powerful and flexible, but cannot solve every single problem. Sometimes refinements on the final version of the encoded or decoded objects are necessary. To give developers more control we introduced a final step hook the that gives access to the full mapper state and is run right before the object is returned by the mapper. 153 | 154 | ````objc 155 | [mapper setFinalMappingDecoder:^(NSDictionary *input, User *object){ 156 | NSLog(@"[Warning] User non mapped keys %@", input); 157 | } forClass:[User class] withOption:CTXPropertyMapperFinalMappingDecoderOptionExcludeAlreadyMappedKeys]; 158 | ```` 159 | 160 | ````objc 161 | [mapper setFinalMappingEncoder:^(NSMutableDictionary *output, User *object){ 162 | NSString *fullName = [NSString stringWithFormat:@"%@ %@", object.firstName, object.lastName]; 163 | [output setValue:fullName forKey:@"fullName"]; 164 | } forClass:[User class]]; 165 | ```` 166 | 167 | ## Automatic Mappings 168 | 169 | Usually the client model has the same structure as the server. To avoid repetitive code, CTXPropertyMapper supports creating models automatically. 170 | 171 | ````objc 172 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]]; 173 | 174 | [mapper addMappings:[CTXPropertyMapper generateMappingsFromClass:[Job class]] 175 | forClass:[Job class]]; 176 | ```` 177 | We use some objective c runtime calls to create a valid mapper, ignoring pointer address, blocks, selectors, etc. 178 | Currently supported properties: 179 | - NSString 180 | - NSNumber 181 | - char 182 | - double 183 | - enum 184 | - float 185 | - int 186 | - long 187 | - short 188 | - signed 189 | - unsigned 190 | 191 | ### Limitations 192 | 193 | Mapping generation doesn't consider inherited properties, created through protocols or dynamically created through the runtime, so use it wisely. 194 | 195 | To support `keyPath` mappings, the CTXPropertyMapper considers all key names containing dots (.) as keyPaths, and does not support keys that originally contain dots. 196 | 197 | ## Helpers 198 | 199 | If your local model shares property names with the remote model, but you don't want to map the whole object like the automatic mapping does, you can use the method `+ (NSDictionary *)generateMappingsWithKeys:(NSArray *)keys`, passing the array of properties that you want to map. 200 | 201 | ````objc 202 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]]; 203 | 204 | [mapper addMappings:[CTXPropertyMapper generateMappingsWithKeys:@[@"title", @"sector"]] 205 | forClass:[Job class]]; 206 | ```` 207 | 208 | ## Validations 209 | 210 | You can add validations to your mappings. 211 | 212 | ````objc 213 | [mapper addMappings:[CTXPropertyMapper generateMappingsFromClass:[Job class]] 214 | forClass:[Job class]]; 215 | ```` 216 | If necessary, you can create your own validations, get inspired by looking at the category `CTXPropertyDescriptor+Validators(h,m)`. 217 | 218 | #### Strings 219 | * isRequired 220 | * matchesRegEx 221 | * length 222 | * minLength 223 | * maxLength 224 | * lengthRange 225 | * oneOf 226 | * equalTo 227 | 228 | #### Numbers 229 | * min 230 | * max 231 | * range 232 | 233 | ## License 234 | 235 | CTXPropertyMapper is released under a MIT License. See LICENSE file for details. 236 | -------------------------------------------------------------------------------- /CTXPropertyMapper.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 639F54E11A8F5D790069D52E /* CTXMultiMappingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 639F54E01A8F5D790069D52E /* CTXMultiMappingTest.m */; }; 11 | 639F54E51A8F5D8E0069D52E /* TestData.json in Resources */ = {isa = PBXBuildFile; fileRef = 639F54E21A8F5D8E0069D52E /* TestData.json */; }; 12 | 639F54E61A8F5D8E0069D52E /* User.m in Sources */ = {isa = PBXBuildFile; fileRef = 639F54E41A8F5D8E0069D52E /* User.m */; }; 13 | AC8E91B11A828D5F0065C8A9 /* CTXPropertyMapper.h in Headers */ = {isa = PBXBuildFile; fileRef = AC8E91B01A828D5F0065C8A9 /* CTXPropertyMapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | AC8E91B71A828D5F0065C8A9 /* CTXPropertyMapper.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC8E91AB1A828D5F0065C8A9 /* CTXPropertyMapper.framework */; }; 15 | AC8E91BE1A828D5F0065C8A9 /* CTXPropertyMapperTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AC8E91BD1A828D5F0065C8A9 /* CTXPropertyMapperTests.m */; }; 16 | AC8E91CF1A828DD70065C8A9 /* CTXPropertyDescriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = AC8E91C71A828DD70065C8A9 /* CTXPropertyDescriptor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | AC8E91D01A828DD70065C8A9 /* CTXPropertyDescriptor.m in Sources */ = {isa = PBXBuildFile; fileRef = AC8E91C81A828DD70065C8A9 /* CTXPropertyDescriptor.m */; }; 18 | AC8E91D11A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.h in Headers */ = {isa = PBXBuildFile; fileRef = AC8E91C91A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.h */; settings = {ATTRIBUTES = (Public, ); }; }; 19 | AC8E91D21A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.m in Sources */ = {isa = PBXBuildFile; fileRef = AC8E91CA1A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.m */; }; 20 | AC8E91D31A828DD70065C8A9 /* CTXPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = AC8E91CB1A828DD70065C8A9 /* CTXPropertyMapper.m */; }; 21 | AC8E91D41A828DD70065C8A9 /* CTXPropertyMapperErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = AC8E91CC1A828DD70065C8A9 /* CTXPropertyMapperErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; 22 | AC8E91D51A828DD70065C8A9 /* CTXPropertyMapperErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = AC8E91CD1A828DD70065C8A9 /* CTXPropertyMapperErrors.m */; }; 23 | AC8E91D61A828DD70065C8A9 /* CTXPropertyMapperModelFactoryProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AC8E91CE1A828DD70065C8A9 /* CTXPropertyMapperModelFactoryProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | AC8E91B81A828D5F0065C8A9 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = AC8E91A21A828D5F0065C8A9 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = AC8E91AA1A828D5F0065C8A9; 32 | remoteInfo = CTXPropertyMapper; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 639F54E01A8F5D790069D52E /* CTXMultiMappingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTXMultiMappingTest.m; sourceTree = ""; }; 38 | 639F54E21A8F5D8E0069D52E /* TestData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TestData.json; sourceTree = ""; }; 39 | 639F54E31A8F5D8E0069D52E /* User.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = User.h; sourceTree = ""; }; 40 | 639F54E41A8F5D8E0069D52E /* User.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = User.m; sourceTree = ""; }; 41 | AC8E91AB1A828D5F0065C8A9 /* CTXPropertyMapper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CTXPropertyMapper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | AC8E91AF1A828D5F0065C8A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | AC8E91B01A828D5F0065C8A9 /* CTXPropertyMapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CTXPropertyMapper.h; sourceTree = ""; }; 44 | AC8E91B61A828D5F0065C8A9 /* CTXPropertyMapperTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CTXPropertyMapperTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | AC8E91BC1A828D5F0065C8A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | AC8E91BD1A828D5F0065C8A9 /* CTXPropertyMapperTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTXPropertyMapperTests.m; sourceTree = ""; }; 47 | AC8E91C71A828DD70065C8A9 /* CTXPropertyDescriptor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTXPropertyDescriptor.h; sourceTree = ""; }; 48 | AC8E91C81A828DD70065C8A9 /* CTXPropertyDescriptor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTXPropertyDescriptor.m; sourceTree = ""; }; 49 | AC8E91C91A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CTXPropertyDescriptor+Validators.h"; sourceTree = ""; }; 50 | AC8E91CA1A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CTXPropertyDescriptor+Validators.m"; sourceTree = ""; }; 51 | AC8E91CB1A828DD70065C8A9 /* CTXPropertyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTXPropertyMapper.m; sourceTree = ""; }; 52 | AC8E91CC1A828DD70065C8A9 /* CTXPropertyMapperErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTXPropertyMapperErrors.h; sourceTree = ""; }; 53 | AC8E91CD1A828DD70065C8A9 /* CTXPropertyMapperErrors.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTXPropertyMapperErrors.m; sourceTree = ""; }; 54 | AC8E91CE1A828DD70065C8A9 /* CTXPropertyMapperModelFactoryProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTXPropertyMapperModelFactoryProtocol.h; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | AC8E91A71A828D5F0065C8A9 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | AC8E91B31A828D5F0065C8A9 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | AC8E91B71A828D5F0065C8A9 /* CTXPropertyMapper.framework in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | AC8E91A11A828D5F0065C8A9 = { 77 | isa = PBXGroup; 78 | children = ( 79 | AC8E91AD1A828D5F0065C8A9 /* CTXPropertyMapper */, 80 | AC8E91BA1A828D5F0065C8A9 /* CTXPropertyMapperTests */, 81 | AC8E91AC1A828D5F0065C8A9 /* Products */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | AC8E91AC1A828D5F0065C8A9 /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | AC8E91AB1A828D5F0065C8A9 /* CTXPropertyMapper.framework */, 89 | AC8E91B61A828D5F0065C8A9 /* CTXPropertyMapperTests.xctest */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | AC8E91AD1A828D5F0065C8A9 /* CTXPropertyMapper */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | AC8E91B01A828D5F0065C8A9 /* CTXPropertyMapper.h */, 98 | AC8E91CB1A828DD70065C8A9 /* CTXPropertyMapper.m */, 99 | AC8E91CE1A828DD70065C8A9 /* CTXPropertyMapperModelFactoryProtocol.h */, 100 | AC8E91C71A828DD70065C8A9 /* CTXPropertyDescriptor.h */, 101 | AC8E91C81A828DD70065C8A9 /* CTXPropertyDescriptor.m */, 102 | AC8E91C91A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.h */, 103 | AC8E91CA1A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.m */, 104 | AC8E91CC1A828DD70065C8A9 /* CTXPropertyMapperErrors.h */, 105 | AC8E91CD1A828DD70065C8A9 /* CTXPropertyMapperErrors.m */, 106 | AC8E91AE1A828D5F0065C8A9 /* Supporting Files */, 107 | ); 108 | path = CTXPropertyMapper; 109 | sourceTree = ""; 110 | }; 111 | AC8E91AE1A828D5F0065C8A9 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | AC8E91AF1A828D5F0065C8A9 /* Info.plist */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | AC8E91BA1A828D5F0065C8A9 /* CTXPropertyMapperTests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 639F54E01A8F5D790069D52E /* CTXMultiMappingTest.m */, 123 | AC8E91BD1A828D5F0065C8A9 /* CTXPropertyMapperTests.m */, 124 | AC8E91BB1A828D5F0065C8A9 /* Supporting Files */, 125 | ); 126 | path = CTXPropertyMapperTests; 127 | sourceTree = ""; 128 | }; 129 | AC8E91BB1A828D5F0065C8A9 /* Supporting Files */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 639F54E21A8F5D8E0069D52E /* TestData.json */, 133 | 639F54E31A8F5D8E0069D52E /* User.h */, 134 | 639F54E41A8F5D8E0069D52E /* User.m */, 135 | AC8E91BC1A828D5F0065C8A9 /* Info.plist */, 136 | ); 137 | name = "Supporting Files"; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXHeadersBuildPhase section */ 143 | AC8E91A81A828D5F0065C8A9 /* Headers */ = { 144 | isa = PBXHeadersBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | AC8E91B11A828D5F0065C8A9 /* CTXPropertyMapper.h in Headers */, 148 | AC8E91CF1A828DD70065C8A9 /* CTXPropertyDescriptor.h in Headers */, 149 | AC8E91D41A828DD70065C8A9 /* CTXPropertyMapperErrors.h in Headers */, 150 | AC8E91D61A828DD70065C8A9 /* CTXPropertyMapperModelFactoryProtocol.h in Headers */, 151 | AC8E91D11A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.h in Headers */, 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXHeadersBuildPhase section */ 156 | 157 | /* Begin PBXNativeTarget section */ 158 | AC8E91AA1A828D5F0065C8A9 /* CTXPropertyMapper */ = { 159 | isa = PBXNativeTarget; 160 | buildConfigurationList = AC8E91C11A828D5F0065C8A9 /* Build configuration list for PBXNativeTarget "CTXPropertyMapper" */; 161 | buildPhases = ( 162 | AC8E91A61A828D5F0065C8A9 /* Sources */, 163 | AC8E91A71A828D5F0065C8A9 /* Frameworks */, 164 | AC8E91A81A828D5F0065C8A9 /* Headers */, 165 | AC8E91A91A828D5F0065C8A9 /* Resources */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = CTXPropertyMapper; 172 | productName = CTXPropertyMapper; 173 | productReference = AC8E91AB1A828D5F0065C8A9 /* CTXPropertyMapper.framework */; 174 | productType = "com.apple.product-type.framework"; 175 | }; 176 | AC8E91B51A828D5F0065C8A9 /* CTXPropertyMapperTests */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = AC8E91C41A828D5F0065C8A9 /* Build configuration list for PBXNativeTarget "CTXPropertyMapperTests" */; 179 | buildPhases = ( 180 | AC8E91B21A828D5F0065C8A9 /* Sources */, 181 | AC8E91B31A828D5F0065C8A9 /* Frameworks */, 182 | AC8E91B41A828D5F0065C8A9 /* Resources */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | AC8E91B91A828D5F0065C8A9 /* PBXTargetDependency */, 188 | ); 189 | name = CTXPropertyMapperTests; 190 | productName = CTXPropertyMapperTests; 191 | productReference = AC8E91B61A828D5F0065C8A9 /* CTXPropertyMapperTests.xctest */; 192 | productType = "com.apple.product-type.bundle.unit-test"; 193 | }; 194 | /* End PBXNativeTarget section */ 195 | 196 | /* Begin PBXProject section */ 197 | AC8E91A21A828D5F0065C8A9 /* Project object */ = { 198 | isa = PBXProject; 199 | attributes = { 200 | LastUpgradeCheck = 0730; 201 | ORGANIZATIONNAME = EF; 202 | TargetAttributes = { 203 | AC8E91AA1A828D5F0065C8A9 = { 204 | CreatedOnToolsVersion = 6.1.1; 205 | }; 206 | AC8E91B51A828D5F0065C8A9 = { 207 | CreatedOnToolsVersion = 6.1.1; 208 | }; 209 | }; 210 | }; 211 | buildConfigurationList = AC8E91A51A828D5F0065C8A9 /* Build configuration list for PBXProject "CTXPropertyMapper" */; 212 | compatibilityVersion = "Xcode 3.2"; 213 | developmentRegion = English; 214 | hasScannedForEncodings = 0; 215 | knownRegions = ( 216 | en, 217 | ); 218 | mainGroup = AC8E91A11A828D5F0065C8A9; 219 | productRefGroup = AC8E91AC1A828D5F0065C8A9 /* Products */; 220 | projectDirPath = ""; 221 | projectRoot = ""; 222 | targets = ( 223 | AC8E91AA1A828D5F0065C8A9 /* CTXPropertyMapper */, 224 | AC8E91B51A828D5F0065C8A9 /* CTXPropertyMapperTests */, 225 | ); 226 | }; 227 | /* End PBXProject section */ 228 | 229 | /* Begin PBXResourcesBuildPhase section */ 230 | AC8E91A91A828D5F0065C8A9 /* Resources */ = { 231 | isa = PBXResourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | AC8E91B41A828D5F0065C8A9 /* Resources */ = { 238 | isa = PBXResourcesBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | 639F54E51A8F5D8E0069D52E /* TestData.json in Resources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXResourcesBuildPhase section */ 246 | 247 | /* Begin PBXSourcesBuildPhase section */ 248 | AC8E91A61A828D5F0065C8A9 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | AC8E91D31A828DD70065C8A9 /* CTXPropertyMapper.m in Sources */, 253 | AC8E91D21A828DD70065C8A9 /* CTXPropertyDescriptor+Validators.m in Sources */, 254 | AC8E91D51A828DD70065C8A9 /* CTXPropertyMapperErrors.m in Sources */, 255 | AC8E91D01A828DD70065C8A9 /* CTXPropertyDescriptor.m in Sources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | AC8E91B21A828D5F0065C8A9 /* Sources */ = { 260 | isa = PBXSourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | 639F54E11A8F5D790069D52E /* CTXMultiMappingTest.m in Sources */, 264 | AC8E91BE1A828D5F0065C8A9 /* CTXPropertyMapperTests.m in Sources */, 265 | 639F54E61A8F5D8E0069D52E /* User.m in Sources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXSourcesBuildPhase section */ 270 | 271 | /* Begin PBXTargetDependency section */ 272 | AC8E91B91A828D5F0065C8A9 /* PBXTargetDependency */ = { 273 | isa = PBXTargetDependency; 274 | target = AC8E91AA1A828D5F0065C8A9 /* CTXPropertyMapper */; 275 | targetProxy = AC8E91B81A828D5F0065C8A9 /* PBXContainerItemProxy */; 276 | }; 277 | /* End PBXTargetDependency section */ 278 | 279 | /* Begin XCBuildConfiguration section */ 280 | AC8E91BF1A828D5F0065C8A9 /* Debug */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_MODULES = YES; 287 | CLANG_ENABLE_OBJC_ARC = YES; 288 | CLANG_WARN_BOOL_CONVERSION = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 298 | COPY_PHASE_STRIP = NO; 299 | CURRENT_PROJECT_VERSION = 1; 300 | ENABLE_STRICT_OBJC_MSGSEND = YES; 301 | ENABLE_TESTABILITY = YES; 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_DYNAMIC_NO_PIC = NO; 304 | GCC_OPTIMIZATION_LEVEL = 0; 305 | GCC_PREPROCESSOR_DEFINITIONS = ( 306 | "DEBUG=1", 307 | "$(inherited)", 308 | ); 309 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 310 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 311 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 312 | GCC_WARN_UNDECLARED_SELECTOR = YES; 313 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 314 | GCC_WARN_UNUSED_FUNCTION = YES; 315 | GCC_WARN_UNUSED_VARIABLE = YES; 316 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 317 | MTL_ENABLE_DEBUG_INFO = YES; 318 | ONLY_ACTIVE_ARCH = YES; 319 | SDKROOT = iphoneos; 320 | TARGETED_DEVICE_FAMILY = "1,2"; 321 | VERSIONING_SYSTEM = "apple-generic"; 322 | VERSION_INFO_PREFIX = ""; 323 | }; 324 | name = Debug; 325 | }; 326 | AC8E91C01A828D5F0065C8A9 /* Release */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 331 | CLANG_CXX_LIBRARY = "libc++"; 332 | CLANG_ENABLE_MODULES = YES; 333 | CLANG_ENABLE_OBJC_ARC = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_UNREACHABLE_CODE = YES; 342 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 343 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 344 | COPY_PHASE_STRIP = YES; 345 | CURRENT_PROJECT_VERSION = 1; 346 | ENABLE_NS_ASSERTIONS = NO; 347 | ENABLE_STRICT_OBJC_MSGSEND = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 351 | GCC_WARN_UNDECLARED_SELECTOR = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 353 | GCC_WARN_UNUSED_FUNCTION = YES; 354 | GCC_WARN_UNUSED_VARIABLE = YES; 355 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 356 | MTL_ENABLE_DEBUG_INFO = NO; 357 | SDKROOT = iphoneos; 358 | TARGETED_DEVICE_FAMILY = "1,2"; 359 | VALIDATE_PRODUCT = YES; 360 | VERSIONING_SYSTEM = "apple-generic"; 361 | VERSION_INFO_PREFIX = ""; 362 | }; 363 | name = Release; 364 | }; 365 | AC8E91C21A828D5F0065C8A9 /* Debug */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | DEFINES_MODULE = YES; 369 | DYLIB_COMPATIBILITY_VERSION = 1; 370 | DYLIB_CURRENT_VERSION = 1; 371 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 372 | INFOPLIST_FILE = CTXPropertyMapper/Info.plist; 373 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 375 | PRODUCT_BUNDLE_IDENTIFIER = "com.ctx.EF.$(PRODUCT_NAME:rfc1034identifier)"; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | SKIP_INSTALL = YES; 378 | }; 379 | name = Debug; 380 | }; 381 | AC8E91C31A828D5F0065C8A9 /* Release */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | DEFINES_MODULE = YES; 385 | DYLIB_COMPATIBILITY_VERSION = 1; 386 | DYLIB_CURRENT_VERSION = 1; 387 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 388 | INFOPLIST_FILE = CTXPropertyMapper/Info.plist; 389 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 390 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 391 | PRODUCT_BUNDLE_IDENTIFIER = "com.ctx.EF.$(PRODUCT_NAME:rfc1034identifier)"; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | SKIP_INSTALL = YES; 394 | }; 395 | name = Release; 396 | }; 397 | AC8E91C51A828D5F0065C8A9 /* Debug */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | FRAMEWORK_SEARCH_PATHS = ( 401 | "$(SDKROOT)/Developer/Library/Frameworks", 402 | "$(inherited)", 403 | ); 404 | GCC_PREPROCESSOR_DEFINITIONS = ( 405 | "DEBUG=1", 406 | "$(inherited)", 407 | ); 408 | INFOPLIST_FILE = CTXPropertyMapperTests/Info.plist; 409 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 410 | PRODUCT_BUNDLE_IDENTIFIER = "com.ctx.EF.$(PRODUCT_NAME:rfc1034identifier)"; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | }; 413 | name = Debug; 414 | }; 415 | AC8E91C61A828D5F0065C8A9 /* Release */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | FRAMEWORK_SEARCH_PATHS = ( 419 | "$(SDKROOT)/Developer/Library/Frameworks", 420 | "$(inherited)", 421 | ); 422 | INFOPLIST_FILE = CTXPropertyMapperTests/Info.plist; 423 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 424 | PRODUCT_BUNDLE_IDENTIFIER = "com.ctx.EF.$(PRODUCT_NAME:rfc1034identifier)"; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | }; 427 | name = Release; 428 | }; 429 | /* End XCBuildConfiguration section */ 430 | 431 | /* Begin XCConfigurationList section */ 432 | AC8E91A51A828D5F0065C8A9 /* Build configuration list for PBXProject "CTXPropertyMapper" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | AC8E91BF1A828D5F0065C8A9 /* Debug */, 436 | AC8E91C01A828D5F0065C8A9 /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Release; 440 | }; 441 | AC8E91C11A828D5F0065C8A9 /* Build configuration list for PBXNativeTarget "CTXPropertyMapper" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | AC8E91C21A828D5F0065C8A9 /* Debug */, 445 | AC8E91C31A828D5F0065C8A9 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | AC8E91C41A828D5F0065C8A9 /* Build configuration list for PBXNativeTarget "CTXPropertyMapperTests" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | AC8E91C51A828D5F0065C8A9 /* Debug */, 454 | AC8E91C61A828D5F0065C8A9 /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Release; 458 | }; 459 | /* End XCConfigurationList section */ 460 | }; 461 | rootObject = AC8E91A21A828D5F0065C8A9 /* Project object */; 462 | } 463 | -------------------------------------------------------------------------------- /CTXPropertyMapperTests/CTXPropertyMapperTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyMapperTests.m 3 | // CTXPropertyMapperTests 4 | // 5 | // Created by Mario on 04/02/2015. 6 | // Copyright (c) 2015 EF. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "CTXPropertyMapper.h" 13 | 14 | @interface ItemClass : NSObject 15 | @property (nonatomic, strong, readonly) NSString *name; 16 | @property (nonatomic, strong, readonly) NSString *title; 17 | @end 18 | @implementation ItemClass 19 | @end 20 | 21 | @interface BaseClass : NSObject 22 | @property (nonatomic, strong, readonly) NSString *uuid; 23 | @property (nonatomic, strong, readonly) NSString *name; 24 | @property (nonatomic, strong, readonly) NSString *title; 25 | @property (nonatomic, assign, readonly) int page; 26 | @property (nonatomic, assign, readonly) int offset; 27 | @property (nonatomic, strong, readonly) NSArray *children; 28 | @property (nonatomic, strong, readonly) NSSet *set; 29 | @property (nonatomic, strong, readonly) void ((^block)(void)); 30 | @property (nonatomic, strong, readonly) id subItem; 31 | @property (nonatomic, retain, readonly) NSNumber *number; 32 | @property (nonatomic, assign, readonly) BOOL boolean; 33 | @property (nonatomic, retain, readonly) ItemClass *itemClass; 34 | @property (nonatomic, retain, readonly) NSOrderedSet *orderedSet; 35 | @property (nonatomic, assign, readonly) BOOL usingCustomInit; 36 | - (instancetype)initWithName:(NSString *)name; 37 | @end 38 | @implementation BaseClass 39 | - (instancetype)initWithName:(NSString *)name 40 | { 41 | if (self = [super init]) { 42 | _name = name; 43 | _usingCustomInit = YES; 44 | } 45 | return self; 46 | } 47 | @end 48 | 49 | @interface SuperClass : BaseClass 50 | @property (nonatomic, strong, readonly) NSString *firstName; 51 | @end 52 | @implementation SuperClass 53 | @end 54 | 55 | 56 | @interface SimpleFactory : NSObject 57 | @end 58 | @implementation SimpleFactory 59 | - (id)instanceForClass:(Class)class withDictionary:(NSDictionary *)dictionary 60 | { 61 | return [[class alloc] initWithName:@"custom name"]; 62 | } 63 | @end 64 | 65 | 66 | 67 | @interface CTXPropertyMapperTests : XCTestCase 68 | 69 | @end 70 | 71 | @implementation CTXPropertyMapperTests 72 | 73 | - (void)setUp 74 | { 75 | [super setUp]; 76 | } 77 | 78 | - (void)tearDown 79 | { 80 | [super tearDown]; 81 | } 82 | 83 | - (void)testBasicParse 84 | { 85 | NSDictionary *source = @{@"id":@"source id", 86 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 87 | @"name":[NSNull null], 88 | @"title":@"source title", 89 | @"boolean":@(YES), 90 | @"items":@[ 91 | @{ 92 | @"name": @"Item Name 0", 93 | @"title": @"Item Title 0", 94 | }, 95 | @{ 96 | @"name": @"Item Name 1", 97 | @"title": @"Item Title 1", 98 | }, 99 | @{ 100 | @"name": @"Item Name 2", 101 | @"title": @"Item Title 2", 102 | } 103 | ]}; 104 | 105 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 106 | 107 | [mapper addMappings:@{ 108 | @"name":CTXProperty(name), 109 | @"title":CTXProperty(title), 110 | } 111 | forClass:[ItemClass class]]; 112 | 113 | [mapper addMappings:@{ 114 | @"id":CTXProperty(uuid), 115 | @"name":CTXProperty(name), 116 | @"title":CTXProperty(title), 117 | @"boolean":CTXProperty(boolean), 118 | @"metadata.pagination.page":CTXProperty(page), 119 | @"items":CTXClass(children, [ItemClass class]) 120 | } 121 | forClass:[BaseClass class]]; 122 | 123 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 124 | 125 | XCTAssert(instance); 126 | XCTAssert([instance.uuid isEqualToString:@"source id"]); 127 | XCTAssert(instance.name == nil); 128 | XCTAssert([instance.title isEqualToString:@"source title"]); 129 | XCTAssert(instance.page == 2); 130 | XCTAssert(instance.boolean == YES); 131 | 132 | ItemClass *item0 = instance.children[0]; 133 | XCTAssert([item0.name isEqualToString:@"Item Name 0"]); 134 | XCTAssert([item0.title isEqualToString:@"Item Title 0"]); 135 | 136 | ItemClass *item1 = instance.children[1]; 137 | XCTAssert([item1.name isEqualToString:@"Item Name 1"]); 138 | XCTAssert([item1.title isEqualToString:@"Item Title 1"]); 139 | 140 | ItemClass *item2 = instance.children[2]; 141 | XCTAssert([item2.name isEqualToString:@"Item Name 2"]); 142 | XCTAssert([item2.title isEqualToString:@"Item Title 2"]); 143 | 144 | 145 | NSDictionary *dict = [mapper exportObject:instance]; 146 | XCTAssert(dict); 147 | XCTAssert([dict[@"id"] isEqualToString:@"source id"]); 148 | XCTAssert([dict[@"boolean"] isEqualToNumber:@(YES)]); 149 | XCTAssert([[dict valueForKeyPath:@"metadata.pagination.page"] isEqualToNumber:@(2)]); 150 | XCTAssert(!dict[@"name"]); 151 | XCTAssert([dict[@"title"] isEqualToString:@"source title"]); 152 | XCTAssert([dict[@"items"][0][@"name"] isEqualToString:@"Item Name 0"]); 153 | XCTAssert([dict[@"items"][0][@"title"] isEqualToString:@"Item Title 0"]); 154 | XCTAssert([dict[@"items"][1][@"name"] isEqualToString:@"Item Name 1"]); 155 | XCTAssert([dict[@"items"][1][@"title"] isEqualToString:@"Item Title 1"]); 156 | XCTAssert([dict[@"items"][2][@"name"] isEqualToString:@"Item Name 2"]); 157 | XCTAssert([dict[@"items"][2][@"title"] isEqualToString:@"Item Title 2"]); 158 | } 159 | 160 | - (void)testMappingsErrors 161 | { 162 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 163 | 164 | NSError *error = nil; 165 | [mapper addMappings:@{ 166 | @"name":CTXProperty(name), 167 | @"title":CTXProperty(title), 168 | @(2):@"testing" 169 | } 170 | forClass:[ItemClass class] error:&error]; 171 | 172 | XCTAssert(error); 173 | XCTAssert(error.code == CTXPropertyMapperErrorCodeInvalidMapperFormat); 174 | XCTAssertNotNil(error.description); 175 | } 176 | 177 | - (void)testValidationErrors 178 | { 179 | NSDictionary *source = @{@"id":@"source id", 180 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 181 | @"name":@"source names", 182 | @"title":[NSNull null]}; 183 | 184 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 185 | 186 | [mapper addMappings:@{ 187 | @"id":CTXProperty(uuid), 188 | @"name":CTXProperty(name), 189 | @"title":CTXProperty(title), 190 | @"metadata.pagination.page":CTXProperty(page), 191 | @"items":CTXClass(children, [ItemClass class]) 192 | } 193 | forClass:[BaseClass class]]; 194 | 195 | NSArray *errors = nil; 196 | [mapper createObjectWithClass:[BaseClass class] fromDictionary:source errors:&errors]; 197 | 198 | XCTAssert(errors); 199 | XCTAssert(errors.count == 1); 200 | 201 | NSError *error = errors.firstObject; 202 | XCTAssert(error.code == CTXPropertyMapperErrorCodeMapperDidNotFound); 203 | XCTAssertNotNil(error.description); 204 | } 205 | 206 | - (void)testEncodeOnly 207 | { 208 | NSDictionary *source = @{@"id":@"source id", 209 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 210 | @"name":@"source names", 211 | @"title":@"source title"}; 212 | 213 | 214 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 215 | 216 | [mapper addMappings:@{ 217 | @"id":CTXProperty(uuid), 218 | @"name":CTXPropertyDecode(name), 219 | @"title":CTXProperty(title), 220 | @"metadata.pagination.page":CTXProperty(page) 221 | } 222 | forClass:[BaseClass class]]; 223 | 224 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 225 | 226 | XCTAssert(instance); 227 | XCTAssert([instance.name isEqualToString:@"source names"]); 228 | 229 | NSDictionary *dict = [mapper exportObject:instance]; 230 | 231 | XCTAssert(dict); 232 | XCTAssert(dict[@"name"] == nil); 233 | } 234 | 235 | - (void)testDecodeOnly 236 | { 237 | NSDictionary *source = @{@"id":@"source id", 238 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 239 | @"name":@"source names", 240 | @"title":@"source title"}; 241 | 242 | 243 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 244 | 245 | [mapper addMappings:@{ 246 | @"id":CTXProperty(uuid), 247 | @"name":CTXPropertyEncode(name), 248 | @"title":CTXProperty(title), 249 | @"metadata.pagination.page":CTXProperty(page) 250 | } 251 | forClass:[BaseClass class]]; 252 | 253 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 254 | 255 | XCTAssert(instance); 256 | XCTAssert(instance.name == nil); 257 | 258 | [instance setValue:@"testing name" forKey:@"name"]; 259 | NSDictionary *dict = [mapper exportObject:instance]; 260 | 261 | XCTAssert(dict); 262 | XCTAssert([dict[@"name"] isEqualToString:@"testing name"]); 263 | } 264 | 265 | - (void)testBlock 266 | { 267 | NSDictionary *source = @{@"id":@"source id", 268 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 269 | @"name":@"source names", 270 | @"title":@"source title"}; 271 | 272 | 273 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 274 | 275 | [mapper addMappings:@{ 276 | @"id":CTXProperty(uuid), 277 | @"name":CTXBlock(name, 278 | ^(id input, NSString *property){return @[@{@"value":input}];}, 279 | ^(id input, NSString *property){return [input stringByAppendingString:@"!"];}), 280 | @"title":CTXProperty(title), 281 | @"metadata.pagination.page":CTXProperty(page) 282 | } 283 | forClass:[BaseClass class]]; 284 | 285 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 286 | 287 | XCTAssert(instance); 288 | XCTAssert([instance.name isEqualToString:@"source names!"]); 289 | 290 | [instance setValue:@"testing name" forKey:@"name"]; 291 | NSDictionary *dict = [mapper exportObject:instance]; 292 | 293 | XCTAssert(dict); 294 | XCTAssert([[dict valueForKeyPath:@"name.value"][0] isEqualToString:@"testing name"]); 295 | } 296 | 297 | - (void)testValidation 298 | { 299 | NSDictionary *source = @{@"id":@"source id", 300 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 301 | @"title":@"source title"}; 302 | 303 | 304 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 305 | 306 | [mapper addMappings:@{ 307 | @"id":CTXProperty(uuid), 308 | @"name":CTXProperty(name).isRequired(), 309 | @"metadata.pagination.page":CTXProperty(page) 310 | } 311 | forClass:[BaseClass class]]; 312 | 313 | NSArray *errors = nil; 314 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source errors:&errors]; 315 | 316 | XCTAssert(!instance); 317 | 318 | NSError *error = errors[0]; 319 | XCTAssert(error.code == CTXPropertyMapperErrorCodeValidationFailed); 320 | XCTAssertNotNil(error.description); 321 | } 322 | 323 | 324 | - (void)testPropertyMapperGenerator 325 | { 326 | BaseClass *base = [[BaseClass alloc] init]; 327 | NSDictionary *dictionary = [CTXPropertyMapper generateMappingsFromClass:[base class]]; 328 | 329 | XCTAssert(dictionary.count == 8); 330 | XCTAssert([dictionary valueForKey:@"name"]); 331 | XCTAssert([dictionary valueForKey:@"number"]); 332 | XCTAssert([dictionary valueForKey:@"page"]); 333 | XCTAssert([dictionary valueForKey:@"offset"]); 334 | XCTAssert([dictionary valueForKey:@"title"]); 335 | XCTAssert([dictionary valueForKey:@"uuid"]); 336 | XCTAssert([dictionary valueForKey:@"boolean"]); 337 | } 338 | 339 | - (void)testSetProperty 340 | { 341 | NSDictionary *source = @{@"name":@"source name", 342 | @"title":@"source title", 343 | @"orderedSet":@[ 344 | @{ 345 | @"name": @"Item Name 0", 346 | @"title": @"Item Title 0", 347 | }, 348 | @{ 349 | @"name": @"Item Name 1", 350 | @"title": @"Item Title 1", 351 | }, 352 | @{ 353 | @"name": @"Item Name 2", 354 | @"title": @"Item Title 2", 355 | } 356 | ], 357 | @"set":@[ 358 | @{ 359 | @"name": @"Item Name 0", 360 | @"title": @"Item Title 0", 361 | }, 362 | @{ 363 | @"name": @"Item Name 1", 364 | @"title": @"Item Title 1", 365 | } 366 | ],}; 367 | 368 | 369 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 370 | 371 | [mapper addMappings:@{ 372 | @"name":CTXProperty(name), 373 | @"set":CTXClass(set, [ItemClass class]), 374 | @"orderedSet":CTXClass(orderedSet, [ItemClass class]) 375 | } 376 | forClass:[BaseClass class]]; 377 | 378 | [mapper addMappings:@{ 379 | @"name":CTXProperty(name), 380 | @"title":CTXProperty(title), 381 | } 382 | forClass:[ItemClass class]]; 383 | 384 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 385 | XCTAssert(instance); 386 | XCTAssert([instance.set count] == 2); 387 | XCTAssert([instance.orderedSet count] == 3); 388 | XCTAssert([instance.set isKindOfClass:NSSet.class]); 389 | XCTAssert([instance.orderedSet isKindOfClass:NSOrderedSet.class]); 390 | 391 | NSDictionary *dict = [mapper exportObject:instance]; 392 | XCTAssert(dict); 393 | XCTAssert([dict[@"set"] count] == 2); 394 | XCTAssert([dict[@"orderedSet"] count] == 3); 395 | 396 | } 397 | 398 | - (void)testUnknownClassProperty 399 | { 400 | NSDictionary *source = @{@"id":@"source id", 401 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 402 | @"title":@"source title"}; 403 | 404 | 405 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 406 | 407 | NSError *error = nil; 408 | 409 | [mapper addMappings:@{ 410 | @"id":CTXProperty(uuid), 411 | @"metadata.pagination.page":CTXProperty(page), 412 | @"unknown":CTXProperty(unknown) 413 | } 414 | forClass:[BaseClass class] 415 | error:&error]; 416 | 417 | XCTAssert(error); 418 | XCTAssert(error.code == CTXPropertyMapperErrorCodeUnknownProperty); 419 | XCTAssertNotNil(error.description); 420 | 421 | NSArray *errors = nil; 422 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source errors:&errors]; 423 | 424 | XCTAssert(!instance); 425 | 426 | error = errors[0]; 427 | XCTAssert(error.code == CTXPropertyMapperErrorCodeMapperDidNotFound); 428 | XCTAssertNotNil(error.description); 429 | } 430 | 431 | - (void)testUnknownDictProperty 432 | { 433 | NSDictionary *source = @{@"id":@"source id", 434 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 435 | @"title":@"source title"}; 436 | 437 | 438 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 439 | 440 | NSError *error = nil; 441 | 442 | [mapper addMappings:@{ 443 | @"title":CTXProperty(title).isRequired(), 444 | @"metadata.pagination.page":CTXProperty(page), 445 | @"unknown":CTXProperty(uuid) 446 | } 447 | forClass:[BaseClass class] 448 | error:&error]; 449 | 450 | XCTAssert(!error); 451 | 452 | NSArray *errors = nil; 453 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source errors:&errors]; 454 | 455 | XCTAssert(instance); 456 | XCTAssert([instance.title isEqualToString:@"source title"]); 457 | XCTAssert(instance.page == 2); 458 | XCTAssert(instance.uuid == nil); 459 | } 460 | 461 | - (void)testSuperClass 462 | { 463 | NSDictionary *source = @{@"id":@"source id", 464 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 465 | @"title":@"source title", 466 | @"firstName":@"source firstName"}; 467 | 468 | 469 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 470 | 471 | NSError *error = nil; 472 | 473 | [mapper addMappings:@{@"id":CTXProperty(uuid), 474 | @"title":CTXProperty(title).isRequired(), 475 | @"metadata.pagination.page":CTXProperty(page), 476 | @"firstName":CTXProperty(firstName) 477 | } 478 | forClass:[SuperClass class] 479 | error:&error]; 480 | 481 | XCTAssert(!error); 482 | 483 | NSArray *errors = nil; 484 | SuperClass *instance = [mapper createObjectWithClass:[SuperClass class] fromDictionary:source errors:&errors]; 485 | 486 | XCTAssert(instance); 487 | XCTAssert([instance.uuid isEqualToString:@"source id"]); 488 | XCTAssert([instance.title isEqualToString:@"source title"]); 489 | XCTAssert([instance.firstName isEqualToString:@"source firstName"]); 490 | } 491 | 492 | - (void)testNilChildObject 493 | { 494 | NSDictionary *source = @{@"id":@"source id", 495 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 496 | @"name":[NSNull null], 497 | @"title":@"source title", 498 | @"item":[NSNull null]}; 499 | 500 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 501 | 502 | [mapper addMappings:@{ 503 | @"name":CTXProperty(name), 504 | @"title":CTXProperty(title), 505 | } 506 | forClass:[ItemClass class]]; 507 | 508 | [mapper addMappings:@{@"id":CTXProperty(uuid), 509 | @"name":CTXProperty(name), 510 | @"title":CTXProperty(title), 511 | @"metadata.pagination.page":CTXProperty(page), 512 | @"item":CTXClass(itemClass, [ItemClass class]) 513 | } 514 | forClass:[BaseClass class]]; 515 | 516 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 517 | 518 | XCTAssert(instance); 519 | XCTAssert(instance.itemClass == nil); 520 | } 521 | 522 | - (void)testFactory 523 | { 524 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] initWithModelFactory:[[SimpleFactory alloc] init]]; 525 | 526 | NSDictionary *source = @{@"id":@"source id", 527 | @"metadata":@{@"pagination":@{@"page":@(2)}}, 528 | @"title":@"source title"}; 529 | 530 | [mapper addMappings:@{@"id":CTXProperty(uuid), 531 | @"title":CTXProperty(title), 532 | @"metadata.pagination.page":CTXProperty(page) 533 | } 534 | forClass:[BaseClass class]]; 535 | 536 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 537 | 538 | XCTAssert(instance); 539 | XCTAssert(instance.usingCustomInit); 540 | } 541 | 542 | - (void)testSetMappings 543 | { 544 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 545 | 546 | NSDictionary *source = @{@"title":@"source title"}; 547 | 548 | [mapper setMappings:@{@"title":CTXProperty(title)} 549 | forClass:[BaseClass class]]; 550 | 551 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 552 | XCTAssert(instance); 553 | XCTAssert(instance.name == nil); 554 | XCTAssert([instance.title isEqualToString:@"source title"]); 555 | 556 | [mapper setMappings:@{@"title":CTXProperty(name)} 557 | forClass:[BaseClass class]]; 558 | 559 | instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 560 | XCTAssert(instance); 561 | XCTAssert(instance.title == nil); 562 | XCTAssert([instance.name isEqualToString:@"source title"]); 563 | } 564 | 565 | - (void)testRemoveMappings 566 | { 567 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 568 | 569 | NSDictionary *source = @{@"title":@"source title"}; 570 | 571 | [mapper setMappings:@{@"title":CTXProperty(title)} 572 | forClass:[BaseClass class]]; 573 | 574 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 575 | XCTAssert(instance); 576 | 577 | [mapper removeMappingsForClass:[BaseClass class]]; 578 | instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 579 | XCTAssert(instance == nil); 580 | } 581 | 582 | - (void)testAsymmetricalMappings 583 | { 584 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 585 | 586 | NSDictionary *source = @{@"title":@"source title", 587 | @"consume":@"consume value"}; 588 | 589 | [mapper setMappings:@{@"title":CTXProperty(title), 590 | @"export":CTXGenerationConsumerBlock(^(id object){return @"exported property";}, nil), 591 | @"consume":CTXGenerationConsumerBlock(nil, ^(id input, id object){ XCTAssert([input isEqualToString:@"consume value"]);})} 592 | forClass:[BaseClass class]]; 593 | 594 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 595 | XCTAssert(instance); 596 | 597 | NSDictionary *dict = [mapper exportObject:instance]; 598 | XCTAssert(dict != nil); 599 | XCTAssert([dict[@"export"] isEqualToString:@"exported property"]); 600 | } 601 | 602 | - (void)testFinalMappings 603 | { 604 | NSDictionary *source = @{@"id" : @"source id", 605 | @"metadata" : @{@"pagination":@{@"page":@(2)}}, 606 | @"name" : [NSNull null], 607 | @"title" : @"some title", 608 | @"item" : [NSNull null], 609 | }; 610 | 611 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 612 | 613 | [mapper addMappings:@{@"name":CTXProperty(name), @"title":CTXProperty(title)} 614 | forClass:[ItemClass class]]; 615 | 616 | [mapper addMappings:@{@"id":CTXProperty(uuid), 617 | @"name":CTXProperty(name), 618 | @"metadata.pagination.page":CTXProperty(page), 619 | @"item":CTXClass(itemClass, [ItemClass class])} 620 | forClass:[BaseClass class]]; 621 | 622 | [mapper setFinalMappingDecoder:^(NSDictionary *input, id object) { 623 | 624 | NSDictionary *expected = @{@"metadata" : @{@"pagination":@{}}, 625 | @"title" : @"some title"}; 626 | XCTAssert([input isEqualToDictionary:expected]); 627 | 628 | [object setValue:input[@"title"] forKeyPath:@"title"]; 629 | 630 | XCTAssert([[object title] isEqualToString:@"some title"]); 631 | 632 | } forClass:[BaseClass class] withOption:CTXPropertyMapperFinalMappingDecoderOptionExcludeAlreadyMappedKeys]; 633 | 634 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source]; 635 | 636 | XCTAssert(instance); 637 | XCTAssert(instance.itemClass == nil); 638 | 639 | [mapper setFinalMappingEncoder:^(NSMutableDictionary *output, id object) { 640 | 641 | output[@"title"] = [object title]; 642 | 643 | } forClass:[BaseClass class]]; 644 | 645 | NSDictionary *exportedObject = [mapper exportObject:instance withOptions:CTXPropertyMapperExportOptionIncludeNullValue]; 646 | 647 | XCTAssert(exportedObject); 648 | XCTAssert([exportedObject isEqualToDictionary:source]); 649 | } 650 | 651 | - (void)testKeyPathEncoding 652 | { 653 | NSDictionary *source = @{@"id" : @"source id", 654 | @"metadata" : @{@"pagination":@{@"page":@(2), @"offset":@(10)}, @"version":@"1.0.0"}, 655 | @"name" : [NSNull null], 656 | @"title" : @"some title", 657 | @"item" : [NSNull null], 658 | }; 659 | 660 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 661 | 662 | [mapper addMappings:@{@"name":CTXProperty(name), @"title":CTXProperty(title)} 663 | forClass:[ItemClass class]]; 664 | 665 | [mapper addMappings:@{@"id":CTXProperty(uuid), 666 | @"name":CTXProperty(name), 667 | @"metadata.pagination.page":CTXProperty(page), 668 | @"metadata.pagination.offset":CTXProperty(offset), 669 | @"item":CTXClass(itemClass, [ItemClass class])} 670 | forClass:[BaseClass class]]; 671 | 672 | NSArray *errors = nil; 673 | BaseClass *instance = [mapper createObjectWithClass:[BaseClass class] fromDictionary:source errors:&errors]; 674 | 675 | NSDictionary *exportedObject = [mapper exportObject:instance]; 676 | 677 | NSDictionary *expectedDictionary = @{@"id" : @"source id", 678 | @"metadata" : @{@"pagination":@{@"page":@(2), @"offset":@(10)}} 679 | }; 680 | 681 | XCTAssert([exportedObject isEqualToDictionary:expectedDictionary]); 682 | } 683 | 684 | - (void)testBooleanJsonEncoding 685 | { 686 | BaseClass *instance = [[BaseClass alloc] initWithName:@"name"]; 687 | 688 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 689 | 690 | [mapper addMappings:@{@"name":CTXProperty(name), 691 | @"usingCustomInit":CTXProperty(usingCustomInit)} 692 | forClass:[BaseClass class]]; 693 | 694 | NSDictionary *exportedObject = [mapper exportObject:instance withOptions:CTXPropertyMapperExportOptionExcludeNullValue]; 695 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:exportedObject options:0 error:nil]; 696 | NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 697 | 698 | XCTAssert(exportedObject); 699 | XCTAssert(jsonString); 700 | XCTAssert([jsonString isEqualToString:@"{\"usingCustomInit\":true,\"name\":\"name\"}"]); 701 | } 702 | 703 | - (void)testIncludeNullExportOption 704 | { 705 | BaseClass *instance = [[BaseClass alloc] initWithName:@"name"]; 706 | 707 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 708 | 709 | [mapper addMappings:@{@"name":CTXProperty(name), 710 | @"usingCustomInit":CTXProperty(usingCustomInit), 711 | @"itemClass":CTXClass(itemClass, [ItemClass class]), 712 | @"title":CTXBlock(title, 713 | ^id(id input, NSString *property){return nil;}, 714 | ^id(id input, NSString *property){return nil;}) 715 | } 716 | forClass:[BaseClass class]]; 717 | 718 | NSDictionary *dictionary = [mapper exportObject:instance withOptions:CTXPropertyMapperExportOptionIncludeNullValue]; 719 | 720 | XCTAssert(dictionary); 721 | XCTAssert([dictionary[@"name"] isEqualToString:@"name"]); 722 | XCTAssert([dictionary[@"usingCustomInit"] isEqualToNumber:@(1)]); 723 | XCTAssert([dictionary[@"title"] isKindOfClass:[NSNull class]]); 724 | XCTAssert([dictionary[@"itemClass"] isKindOfClass:[NSNull class]]); 725 | } 726 | 727 | - (void)testExcludeNullExportOption 728 | { 729 | BaseClass *instance = [[BaseClass alloc] initWithName:@"name"]; 730 | 731 | CTXPropertyMapper *mapper = [[CTXPropertyMapper alloc] init]; 732 | 733 | [mapper addMappings:@{@"name":CTXProperty(name), 734 | @"usingCustomInit":CTXProperty(usingCustomInit), 735 | @"itemClass":CTXClass(itemClass, [ItemClass class]), 736 | @"title":CTXBlock(title, 737 | ^id(id input, NSString *property){return nil;}, 738 | ^id(id input, NSString *property){return nil;}) 739 | } 740 | forClass:[BaseClass class]]; 741 | 742 | NSDictionary *dictionary = [mapper exportObject:instance withOptions:CTXPropertyMapperExportOptionExcludeNullValue]; 743 | 744 | XCTAssert(dictionary); 745 | XCTAssert([dictionary[@"name"] isEqualToString:@"name"]); 746 | XCTAssert([dictionary[@"usingCustomInit"] isEqualToNumber:@(1)]); 747 | XCTAssert(dictionary[@"title"] == nil); 748 | XCTAssert(dictionary[@"itemClass"] == nil); 749 | } 750 | 751 | @end 752 | -------------------------------------------------------------------------------- /CTXPropertyMapper/CTXPropertyMapper.m: -------------------------------------------------------------------------------- 1 | // 2 | // CTXPropertyMapper.m 3 | // CTXFramework 4 | // 5 | // Created by Mario on 09/12/2014. 6 | // Copyright (c) 2015 EF. All rights reserved. 7 | // 8 | 9 | #import "CTXPropertyMapper.h" 10 | #import "CTXPropertyDescriptor.h" 11 | #import "CTXPropertyMapperErrors.h" 12 | #import "objc/runtime.h" 13 | 14 | @interface NSObject (Properties) 15 | 16 | + (NSDictionary *)propertiesDictionaryFromClass:(Class)clazz; 17 | - (id)wrappedValueForKey:(NSString *)key; 18 | 19 | @end 20 | 21 | 22 | @interface NSDictionary (CTXMutableDeepCopy) 23 | 24 | - (NSMutableDictionary *)ctx_deepCopy; 25 | - (NSMutableDictionary *)ctx_mutableDeepCopy; 26 | 27 | @end 28 | 29 | 30 | @interface NSMutableDictionary (CTXSetSafeValueForKey) 31 | 32 | - (void)ctx_setSafeValue:(id)value forKeyPath:(NSString *)keyPath withOptions:(enum CTXPropertyMapperExportOption)options; 33 | 34 | @end 35 | 36 | 37 | @interface NSArray (CTXMutableDeepCopy) 38 | 39 | - (NSMutableArray *)ctx_deepCopy; 40 | - (NSMutableArray *)ctx_mutableDeepCopy; 41 | 42 | @end 43 | 44 | 45 | @interface NSMutableArray (CTXSetSafeValueForKey) 46 | 47 | - (void)ctx_safeAddObject:(id)object; 48 | 49 | @end 50 | 51 | 52 | @interface CTXPropertyMapperSimpleModelFactory : NSObject 53 | 54 | @end 55 | 56 | @interface CTXPropertyMapper() 57 | 58 | @property (nonatomic, strong) id modelFactory; 59 | @property (nonatomic, strong) NSMutableDictionary *cachedPropertiesByClass; 60 | @property (nonatomic, strong) NSMutableDictionary *mappingsByClass; 61 | 62 | @property (nonatomic, strong) NSMapTable *finalMappingEncodersByClass; 63 | @property (nonatomic, strong) NSMapTable *finalMappingDecodersByClass; 64 | @property (nonatomic, strong) NSMutableDictionary *finalMappingDecoderOptionByClass; 65 | 66 | @end 67 | 68 | 69 | @implementation CTXPropertyMapper 70 | 71 | #pragma mark - Public Methods 72 | 73 | - (instancetype)init 74 | { 75 | if (self = [super init]) { 76 | _mappingsByClass = [NSMutableDictionary dictionary]; 77 | _cachedPropertiesByClass = [NSMutableDictionary dictionary]; 78 | _modelFactory = [[CTXPropertyMapperSimpleModelFactory alloc] init]; 79 | 80 | _finalMappingEncodersByClass = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsCopyIn]; 81 | _finalMappingDecodersByClass = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsCopyIn]; 82 | _finalMappingDecoderOptionByClass = [NSMutableDictionary dictionary]; 83 | } 84 | return self; 85 | } 86 | 87 | - (instancetype)initWithModelFactory:(id)modelFactory 88 | { 89 | NSParameterAssert(modelFactory); 90 | 91 | if (self = [super init]) { 92 | _mappingsByClass = [NSMutableDictionary dictionary]; 93 | _cachedPropertiesByClass = [NSMutableDictionary dictionary]; 94 | _modelFactory = modelFactory; 95 | } 96 | return self; 97 | } 98 | 99 | - (BOOL)addMappings:(NSDictionary *)mappings forClass:(Class)clazz 100 | { 101 | return [self addMappings:mappings forClass:clazz error:nil]; 102 | } 103 | 104 | - (BOOL)addMappings:(NSDictionary *)mappings forClass:(Class)clazz error:(NSError *__autoreleasing*)error 105 | { 106 | NSError *mappingError = [self _validateMappings:mappings forClass:clazz]; 107 | 108 | if (error != NULL) { 109 | *error = mappingError; 110 | } 111 | 112 | if (!mappingError) { 113 | if (!self.mappingsByClass[NSStringFromClass(clazz)]) { 114 | self.mappingsByClass[NSStringFromClass(clazz)] = [NSMutableDictionary dictionary]; 115 | } 116 | [self.mappingsByClass[NSStringFromClass(clazz)] addEntriesFromDictionary:mappings]; 117 | } 118 | 119 | return !mappingError; 120 | } 121 | 122 | - (void)addMappingsFromPropertyMapper:(CTXPropertyMapper *)propertyMapper 123 | { 124 | [self.mappingsByClass addEntriesFromDictionary:[propertyMapper mappingsByClass]]; 125 | 126 | for(NSString *className in propertyMapper.finalMappingEncodersByClass.keyEnumerator) { 127 | [self.finalMappingEncodersByClass setObject:[propertyMapper.finalMappingEncodersByClass objectForKey:className] forKey:className]; 128 | } 129 | 130 | for(NSString *className in propertyMapper.finalMappingDecodersByClass.keyEnumerator) { 131 | [self.finalMappingDecodersByClass setObject:[propertyMapper.finalMappingDecodersByClass objectForKey:className] forKey:className]; 132 | } 133 | 134 | [propertyMapper.finalMappingDecoderOptionByClass enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 135 | [self.finalMappingDecoderOptionByClass setObject:obj forKey:key]; 136 | }]; 137 | } 138 | 139 | - (BOOL)setMappings:(NSDictionary *)mappings forClass:(Class)clazz 140 | { 141 | return [self setMappings:mappings forClass:clazz error:nil]; 142 | } 143 | 144 | - (BOOL)setMappings:(NSDictionary *)mappings forClass:(Class)clazz error:(NSError *__autoreleasing*)error 145 | { 146 | NSError *mappingError = [self _validateMappings:mappings forClass:clazz]; 147 | 148 | if (error != NULL) { 149 | *error = mappingError; 150 | } 151 | 152 | if (!mappingError) { 153 | self.mappingsByClass[NSStringFromClass(clazz)] = [mappings copy]; 154 | } 155 | 156 | return !mappingError; 157 | } 158 | 159 | - (void)setFinalMappingEncoder:(CTXFinalMappingEncoderBlock)encoder forClass:(Class)clazz 160 | { 161 | [self.finalMappingEncodersByClass setObject:encoder forKey:NSStringFromClass(clazz)]; 162 | } 163 | 164 | - (void)setFinalMappingDecoder:(CTXFinalMappingDecoderBlock)decoder forClass:(Class)clazz withOption:(CTXPropertyMapperFinalMappingDecoderOption)option 165 | { 166 | [self.finalMappingDecodersByClass setObject:decoder forKey:NSStringFromClass(clazz)]; 167 | [self.finalMappingDecoderOptionByClass setObject:@(option) forKey:NSStringFromClass(clazz)]; 168 | } 169 | 170 | - (BOOL)removeMappingsForClass:(Class)clazz 171 | { 172 | BOOL success = NO; 173 | 174 | if (self.mappingsByClass[NSStringFromClass(clazz)]) { 175 | [self.mappingsByClass removeObjectForKey:NSStringFromClass(clazz)]; 176 | success = YES; 177 | } 178 | 179 | if([self.finalMappingEncodersByClass objectForKey:NSStringFromClass(clazz)]) { 180 | [self.finalMappingEncodersByClass removeObjectForKey:NSStringFromClass(clazz)]; 181 | success = YES; 182 | } 183 | 184 | if([self.finalMappingDecodersByClass objectForKey:NSStringFromClass(clazz)]) { 185 | [self.finalMappingDecodersByClass removeObjectForKey:NSStringFromClass(clazz)]; 186 | [self.finalMappingDecoderOptionByClass removeObjectForKey:NSStringFromClass(clazz)]; 187 | success = YES; 188 | } 189 | 190 | return success; 191 | } 192 | 193 | - (id)createObjectWithClass:(Class)clazz fromDictionary:(NSDictionary *)dictionary 194 | { 195 | return [self createObjectWithClass:clazz fromDictionary:dictionary errors:nil]; 196 | } 197 | 198 | - (id)createObjectWithClass:(Class)clazz fromDictionary:(NSDictionary *)dictionary errors:(NSArray *__autoreleasing*)errors 199 | { 200 | NSDictionary *mappings = self.mappingsByClass[NSStringFromClass(clazz)]; 201 | NSArray *validationErrors = nil; 202 | 203 | if (!mappings) { 204 | NSString *description = [NSString stringWithFormat:CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCodeMapperDidNotFound), NSStringFromClass(clazz)]; 205 | 206 | NSError *error = [NSError errorWithDomain:kCTXPropertyMapperErrorDomain 207 | code:CTXPropertyMapperErrorCodeMapperDidNotFound 208 | userInfo:@{NSLocalizedDescriptionKey:description}]; 209 | validationErrors = @[error]; 210 | } else { 211 | validationErrors = [self _validateMapping:mappings withValues:dictionary]; 212 | } 213 | 214 | if (validationErrors.count > 0) { 215 | if (errors != NULL) { 216 | *errors = validationErrors; 217 | } 218 | return nil; 219 | } 220 | 221 | return [self _createObjectWithClass:clazz fromDictionary:dictionary]; 222 | } 223 | 224 | - (NSDictionary *)exportObject:(id)object 225 | { 226 | return [self exportObject:object withOptions:CTXPropertyMapperExportOptionExcludeNullValue]; 227 | } 228 | 229 | - (NSDictionary *)exportObject:(id)object withOptions:(enum CTXPropertyMapperExportOption)options 230 | { 231 | if(object == [NSNull null]) { 232 | return object; 233 | } else if(object == nil) { 234 | return nil; 235 | } 236 | 237 | NSDictionary *mappings = self.mappingsByClass[NSStringFromClass([object class])]; 238 | NSMutableDictionary *exportedObject = [NSMutableDictionary dictionary]; 239 | 240 | [mappings enumerateKeysAndObjectsUsingBlock:^(NSString *key, CTXPropertyDescriptor *descriptor, BOOL *stop) { 241 | if ((descriptor.mode & CTXPropertyMapperCodificationModeEncode) == CTXPropertyMapperCodificationModeEncode) { 242 | NSArray *parts = [key componentsSeparatedByString:@"."]; 243 | __block NSMutableDictionary *currentDictionary = exportedObject; 244 | [parts enumerateObjectsUsingBlock:^(NSString *part, NSUInteger index, BOOL *stp) { 245 | 246 | NSString *keyPath = [[parts subarrayWithRange:NSMakeRange(0, index + 1)] componentsJoinedByString:@"."]; 247 | 248 | if (index == parts.count - 1) { 249 | id value = [self _getSafeValueForKey:descriptor.propertyName atObject:object]; 250 | 251 | switch (descriptor.type) { 252 | case CTXPropertyDescriptorTypeDirect: 253 | { 254 | [currentDictionary ctx_setSafeValue:value forKeyPath:keyPath withOptions:options]; 255 | } break; 256 | case CTXPropertyDescriptorTypeClass: 257 | { 258 | 259 | if ([value isKindOfClass:NSSet.class]) { 260 | NSMutableArray *items = [NSMutableArray array]; 261 | [(NSSet *)value enumerateObjectsUsingBlock:^(id obj, BOOL *s) { 262 | [items addObject:[self exportObject:obj]]; 263 | }]; 264 | [currentDictionary ctx_setSafeValue:items forKeyPath:keyPath withOptions:options]; 265 | }else if ([value isKindOfClass:NSArray.class] || [value isKindOfClass:NSOrderedSet.class]) { 266 | NSMutableArray *items = [NSMutableArray array]; 267 | [value enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *s) { 268 | [items addObject:[self exportObject:obj]]; 269 | }]; 270 | [currentDictionary ctx_setSafeValue:items forKeyPath:keyPath withOptions:options]; 271 | } else { 272 | [currentDictionary ctx_setSafeValue:[self exportObject:value withOptions:options] forKeyPath:keyPath withOptions:options]; 273 | } 274 | } break; 275 | case CTXPropertyDescriptorTypeSymmetricalBlock: 276 | { 277 | [currentDictionary ctx_setSafeValue:descriptor.encodingBlock(value, key) forKeyPath:keyPath withOptions:options]; 278 | } break; 279 | case CTXPropertyDescriptorTypeAsymmetricalBlock: 280 | { 281 | [currentDictionary ctx_setSafeValue:descriptor.encodingGenerationBlock(object) forKeyPath:keyPath withOptions:options]; 282 | } break; 283 | } 284 | } else { 285 | if (![currentDictionary valueForKeyPath:keyPath]) { 286 | [currentDictionary setValue:[NSMutableDictionary dictionary] forKeyPath:keyPath]; 287 | } 288 | } 289 | }]; 290 | } 291 | }]; 292 | 293 | CTXFinalMappingEncoderBlock encoder = [self.finalMappingEncodersByClass objectForKey:NSStringFromClass([object class])]; 294 | if(encoder) { 295 | NSMutableDictionary *mutableExportedObject = [exportedObject ctx_mutableDeepCopy]; 296 | encoder(mutableExportedObject, object); 297 | [exportedObject addEntriesFromDictionary:[mutableExportedObject ctx_deepCopy]]; 298 | } 299 | 300 | return exportedObject; 301 | } 302 | 303 | + (NSDictionary *)generateMappingsFromClass:(Class)clazz 304 | { 305 | NSMutableDictionary *mappings = [NSMutableDictionary dictionary]; 306 | 307 | NSArray *allowedTypes = @[@"NSNumber", @"NSString", @"c", @"d", @"i", @"f", @"l", @"s", @"I"]; 308 | 309 | NSDictionary *properties = [NSObject propertiesDictionaryFromClass:clazz]; 310 | 311 | for (NSString *property in [properties allKeys]) { 312 | NSString *type = properties[property]; 313 | 314 | if ([allowedTypes containsObject:type]) { 315 | [mappings setValue:[[CTXPropertyDescriptor alloc] initWithSelector:NSSelectorFromString(property)] forKey:property]; 316 | } 317 | } 318 | 319 | return mappings; 320 | } 321 | 322 | + (NSDictionary *)generateMappingsWithKeys:(NSArray *)keys 323 | { 324 | NSMutableDictionary *mappings = [NSMutableDictionary dictionary]; 325 | for (NSString *key in keys) { 326 | [mappings setValue:[[CTXPropertyDescriptor alloc] initWithSelector:NSSelectorFromString(key)] forKey:key]; 327 | } 328 | return mappings; 329 | } 330 | 331 | #pragma mark - Private Methods 332 | 333 | - (NSDictionary *)_propertiesForClass:(Class)clazz 334 | { 335 | NSDictionary *properties = self.cachedPropertiesByClass[NSStringFromClass(clazz)]; 336 | 337 | if (!properties) { 338 | 339 | NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionary]; 340 | 341 | Class currentClass = clazz; 342 | while (currentClass) { 343 | [mutableDictionary addEntriesFromDictionary:[NSObject propertiesDictionaryFromClass:currentClass]]; 344 | currentClass = class_getSuperclass(currentClass); 345 | } 346 | 347 | properties = mutableDictionary; 348 | 349 | self.cachedPropertiesByClass[NSStringFromClass(clazz)] = properties; 350 | } 351 | 352 | return properties; 353 | } 354 | 355 | - (id)_getSafeValueForKey:(NSString *)key atObject:(id)object 356 | { 357 | if (!key) { 358 | return nil; 359 | } 360 | 361 | //TODO: Improve getValue verification, do not allowing unexpected properties like blocks and selectors 362 | return [object wrappedValueForKey:key]; 363 | } 364 | 365 | - (void)_setSafeValue:(id)value forKey:(NSString *)key toObject:(id)object 366 | { 367 | if (value == nil || value == [NSNull null]) { 368 | [object setValue:nil forKey:key]; 369 | return; 370 | } 371 | 372 | //TODO: Improve setValue verification 373 | 374 | if ([value isKindOfClass:NSArray.class]) { 375 | NSDictionary *properties = [self _propertiesForClass:[object class]]; 376 | //TODO: Actually doesn't support subclasses of the follow entities 377 | if ([properties[key] isEqualToString:NSStringFromClass([NSArray class])] || [properties[key] isEqualToString:NSStringFromClass([NSMutableArray class])]) { 378 | [object setValue:value forKey:key]; 379 | } else if ([properties[key] isEqualToString:NSStringFromClass([NSSet class])] || [properties[key] isEqualToString:NSStringFromClass([NSMutableSet class])]) { 380 | [object setValue:[NSMutableSet setWithArray:value] forKey:key]; 381 | } else if ([properties[key] isEqualToString:NSStringFromClass([NSOrderedSet class])] || [properties[key] isEqualToString:NSStringFromClass([NSMutableOrderedSet class])]){ 382 | [object setValue:[NSMutableOrderedSet orderedSetWithArray:value] forKey:key]; 383 | } 384 | 385 | } else { 386 | [object setValue:value forKey:key]; 387 | } 388 | } 389 | 390 | 391 | - (id)_createObjectWithClass:(Class)clazz fromDictionary:(NSDictionary *)dictionary 392 | { 393 | if(![dictionary isKindOfClass:[NSDictionary class]]) { 394 | return nil; 395 | } 396 | 397 | NSDictionary *mappings = self.mappingsByClass[NSStringFromClass(clazz)]; 398 | 399 | id instance = [self.modelFactory instanceForClass:clazz withDictionary:dictionary]; 400 | 401 | [mappings enumerateKeysAndObjectsUsingBlock:^(NSString *key, CTXPropertyDescriptor *descriptor, BOOL *stop) { 402 | if ([dictionary valueForKeyPath:key] && 403 | (descriptor.mode & CTXPropertyMapperCodificationModeDecode) == CTXPropertyMapperCodificationModeDecode) { 404 | switch (descriptor.type) { 405 | case CTXPropertyDescriptorTypeDirect: 406 | { 407 | [self _setSafeValue:[dictionary valueForKeyPath:key] forKey:descriptor.propertyName toObject:instance]; 408 | } break; 409 | case CTXPropertyDescriptorTypeClass: 410 | { 411 | id value = [dictionary valueForKeyPath:key]; 412 | if ([value isKindOfClass:NSDictionary.class]) { 413 | id subInstance = [self _createObjectWithClass:descriptor.propertyClass fromDictionary:value]; 414 | [self _setSafeValue:subInstance forKey:descriptor.propertyName toObject:instance]; 415 | } else if ([value isKindOfClass:NSArray.class]) { 416 | NSMutableArray *items = [NSMutableArray array]; 417 | [value enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *s) { 418 | [items ctx_safeAddObject:[self _createObjectWithClass:descriptor.propertyClass fromDictionary:obj]]; 419 | }]; 420 | [self _setSafeValue:items forKey:descriptor.propertyName toObject:instance]; 421 | } 422 | } break; 423 | case CTXPropertyDescriptorTypeSymmetricalBlock: 424 | { 425 | [self _setSafeValue:descriptor.decodingBlock([dictionary valueForKeyPath:key], descriptor.propertyName) forKey:descriptor.propertyName toObject:instance]; 426 | } break; 427 | case CTXPropertyDescriptorTypeAsymmetricalBlock: 428 | { 429 | descriptor.decodingConsumerBlock([dictionary valueForKeyPath:key], instance); 430 | } break; 431 | } 432 | } 433 | }]; 434 | 435 | 436 | CTXFinalMappingDecoderBlock finalDecoder = [self.finalMappingDecodersByClass objectForKey:NSStringFromClass(clazz)]; 437 | if(finalDecoder) { 438 | CTXPropertyMapperFinalMappingDecoderOption finalDecoderOption = [[self.finalMappingDecoderOptionByClass objectForKey:NSStringFromClass(clazz)] unsignedIntegerValue]; 439 | 440 | if(finalDecoderOption == CTXPropertyMapperFinalMappingDecoderOptionIncludeAllKeys) { 441 | finalDecoder(dictionary, instance); 442 | } else if(finalDecoderOption == CTXPropertyMapperFinalMappingDecoderOptionExcludeAlreadyMappedKeys) { 443 | 444 | 445 | NSMutableDictionary *filteredDictionary = [dictionary ctx_mutableDeepCopy]; 446 | 447 | for(NSString *key in mappings.allKeys) { 448 | 449 | [filteredDictionary removeObjectForKey:key]; 450 | 451 | NSRange keyPathSeparatorRange = [key rangeOfString:@"." options:NSBackwardsSearch]; 452 | if(keyPathSeparatorRange.location != NSNotFound) { 453 | NSString *basePath = [key substringWithRange:NSMakeRange(0, keyPathSeparatorRange.location)]; 454 | NSString *lastPathComponent = [key substringWithRange:NSMakeRange(keyPathSeparatorRange.location + keyPathSeparatorRange.length, key.length - (keyPathSeparatorRange.location + keyPathSeparatorRange.length))]; 455 | [[filteredDictionary valueForKeyPath:basePath] removeObjectForKey:lastPathComponent]; 456 | } 457 | } 458 | 459 | finalDecoder([filteredDictionary ctx_deepCopy], instance); 460 | } 461 | } 462 | 463 | return instance; 464 | } 465 | 466 | #pragma mark - Validations 467 | 468 | - (NSError *)_validateMappings:(NSDictionary *)mappings forClass:(Class)clazz 469 | { 470 | NSParameterAssert(mappings); 471 | NSParameterAssert(clazz); 472 | 473 | __block NSError *mappingError; 474 | 475 | NSDictionary *properties = [self _propertiesForClass:clazz]; 476 | 477 | [mappings enumerateKeysAndObjectsUsingBlock:^(id key, CTXPropertyDescriptor *descriptor, BOOL *stop) { 478 | 479 | if (![key isKindOfClass:[NSString class]] || ![descriptor isKindOfClass:[CTXPropertyDescriptor class]]) { 480 | mappingError = [NSError errorWithDomain:kCTXPropertyMapperErrorDomain 481 | code:CTXPropertyMapperErrorCodeInvalidMapperFormat 482 | userInfo:@{NSLocalizedDescriptionKey:CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCodeInvalidMapperFormat)}]; 483 | *stop = YES; 484 | } else if (descriptor.type != CTXPropertyDescriptorTypeAsymmetricalBlock && !properties[descriptor.propertyName]) { 485 | NSString *author = [NSString stringWithFormat:CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCodeUnknownProperty), 486 | descriptor.propertyName, NSStringFromClass(clazz)]; 487 | mappingError = [NSError errorWithDomain:kCTXPropertyMapperErrorDomain 488 | code:CTXPropertyMapperErrorCodeUnknownProperty 489 | userInfo:@{NSLocalizedDescriptionKey:author}]; 490 | *stop = YES; 491 | } 492 | }]; 493 | 494 | return mappingError; 495 | } 496 | 497 | - (NSArray *)_validateMapping:(NSDictionary *)mapping withValues:(id)values 498 | { 499 | NSParameterAssert(mapping); 500 | 501 | if (!values) { 502 | return nil; 503 | } 504 | 505 | if ([values isKindOfClass:NSDictionary.class]) { 506 | return [self _validateMapping:mapping withValuesDictionary:values]; 507 | } else if ([values isKindOfClass:NSArray.class]) { 508 | return [self _validateMapping:mapping withValuesArray:values]; 509 | } 510 | return nil; 511 | } 512 | 513 | - (NSArray *)_validateMapping:(NSDictionary *)mapping withValuesArray:(NSArray *)values 514 | { 515 | NSParameterAssert([values isKindOfClass:NSArray.class]); 516 | 517 | NSMutableArray *errors = [NSMutableArray new]; 518 | 519 | [values enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) { 520 | if ([value isKindOfClass:NSArray.class] || [value isKindOfClass:NSDictionary.class]) { 521 | NSArray *validationErrors = [self _validateMapping:mapping withValues:value]; 522 | if (validationErrors) { 523 | [errors addObjectsFromArray:validationErrors]; 524 | } 525 | } 526 | }]; 527 | 528 | return errors; 529 | } 530 | 531 | - (NSArray *)_validateMapping:(NSDictionary *)mapping withValuesDictionary:(NSDictionary *)values 532 | { 533 | NSParameterAssert([values isKindOfClass:NSDictionary.class]); 534 | 535 | NSMutableArray *errors = [NSMutableArray new]; 536 | [mapping enumerateKeysAndObjectsUsingBlock:^(NSString *key, CTXPropertyDescriptor *descriptor, BOOL *stop) { 537 | id value = [values valueForKeyPath:key]; 538 | if ([value isKindOfClass:NSNull.class]) { 539 | value = nil; 540 | } 541 | 542 | switch (descriptor.type) { 543 | case CTXPropertyDescriptorTypeClass: 544 | { 545 | NSDictionary *subMapping = self.mappingsByClass[NSStringFromClass(descriptor.propertyClass)]; 546 | 547 | if (subMapping) { 548 | NSArray *validationErrors = [self _validateMapping:subMapping withValues:value]; 549 | if (validationErrors) { 550 | [errors addObjectsFromArray:validationErrors]; 551 | } 552 | } else { 553 | NSString *description = [NSString stringWithFormat:CTXPropertyMapperErrorDescription(CTXPropertyMapperErrorCodeMapperDidNotFound), NSStringFromClass(descriptor.propertyClass)]; 554 | 555 | NSError *error = [NSError errorWithDomain:kCTXPropertyMapperErrorDomain 556 | code:CTXPropertyMapperErrorCodeMapperDidNotFound 557 | userInfo:@{NSLocalizedDescriptionKey:description}]; 558 | [errors addObject:error]; 559 | } 560 | } break; 561 | default: 562 | { 563 | NSArray *validationErrors = [descriptor validateValue:value]; 564 | if (validationErrors.count > 0) { 565 | [errors addObjectsFromArray:validationErrors]; 566 | } 567 | } break; 568 | } 569 | }]; 570 | 571 | return errors; 572 | } 573 | 574 | @end 575 | 576 | 577 | @implementation CTXPropertyMapperSimpleModelFactory 578 | 579 | - (id)instanceForClass:(Class)class withDictionary:(NSDictionary *)dictionary 580 | { 581 | return [[class alloc] init]; 582 | } 583 | 584 | @end 585 | 586 | 587 | @implementation NSObject (Properties) 588 | 589 | NSString * getPropertyType(objc_property_t property) { 590 | const char *attributes = property_getAttributes(property); 591 | char buffer[1 + strlen(attributes)]; 592 | strcpy(buffer, attributes); 593 | char *state = buffer, *attribute; 594 | while ((attribute = strsep(&state, ",")) != NULL) { 595 | if (attribute[0] == 'T' && attribute[1] != '@') { 596 | // it's a C primitive type: 597 | /* 598 | if you want a list of what will be returned for these primitives, search online for 599 | "objective-c" "Property Attribute Description Examples" 600 | apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc. 601 | */ 602 | return [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding]; 603 | } 604 | else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) { 605 | // it's an ObjC id type: 606 | return @"id"; 607 | } 608 | else if (attribute[0] == 'T' && attribute[1] == '@' && attribute[2] == '?') { 609 | // it's an ObjC id type: 610 | return @"block"; 611 | } 612 | else if (attribute[0] == 'T' && attribute[1] == '@') { 613 | // it's another ObjC object type: 614 | return [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding]; 615 | } 616 | } 617 | return @""; 618 | } 619 | 620 | + (NSDictionary *)propertiesDictionaryFromClass:(Class)clazz 621 | { 622 | NSMutableDictionary *results = [[NSMutableDictionary alloc] init]; 623 | 624 | unsigned int outCount, i; 625 | objc_property_t *properties = class_copyPropertyList(clazz, &outCount); 626 | for (i = 0; i < outCount; i++) { 627 | objc_property_t property = properties[i]; 628 | const char *propName = property_getName(property); 629 | if(propName) { 630 | NSString *propertyName = [NSString stringWithUTF8String:propName]; 631 | NSString *propertyType = getPropertyType(property); 632 | [results setObject:propertyType forKey:propertyName]; 633 | } 634 | } 635 | free(properties); 636 | 637 | return [NSDictionary dictionaryWithDictionary:results]; 638 | } 639 | 640 | - (id)wrappedValueForKey:(NSString *)key 641 | { 642 | NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:NSSelectorFromString(key)]; 643 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 644 | [invocation setSelector:NSSelectorFromString(key)]; 645 | [invocation setTarget:self]; 646 | [invocation invoke]; 647 | 648 | #define WRAP_AND_RETURN(type) do { type val = 0; [invocation getReturnValue:&val]; return @(val); } while (0) 649 | 650 | const char *returnType = [signature methodReturnType]; 651 | 652 | if (returnType[0] == _C_CONST){ 653 | return nil; 654 | } else if (strcmp(returnType, @encode(id)) == 0 || strcmp(returnType, @encode(Class)) == 0) { 655 | __autoreleasing id returnObj; 656 | [invocation getReturnValue:&returnObj]; 657 | return returnObj; 658 | } else if (strcmp(returnType, @encode(SEL)) == 0) { 659 | SEL selector = 0; 660 | [invocation getReturnValue:&selector]; 661 | return NSStringFromSelector(selector); 662 | } else if (strcmp(returnType, @encode(Class)) == 0) { 663 | __autoreleasing Class theClass = Nil; 664 | [invocation getReturnValue:&theClass]; 665 | return theClass; 666 | } else if (strcmp(returnType, @encode(int)) == 0) { 667 | WRAP_AND_RETURN(int); 668 | } else if (strcmp(returnType, @encode(short)) == 0) { 669 | WRAP_AND_RETURN(short); 670 | } else if (strcmp(returnType, @encode(long)) == 0) { 671 | WRAP_AND_RETURN(long); 672 | } else if (strcmp(returnType, @encode(long long)) == 0) { 673 | WRAP_AND_RETURN(long long); 674 | } else if (strcmp(returnType, @encode(unsigned char)) == 0) { 675 | WRAP_AND_RETURN(unsigned char); 676 | } else if (strcmp(returnType, @encode(unsigned int)) == 0) { 677 | WRAP_AND_RETURN(unsigned int); 678 | } else if (strcmp(returnType, @encode(unsigned short)) == 0) { 679 | WRAP_AND_RETURN(unsigned short); 680 | } else if (strcmp(returnType, @encode(unsigned long)) == 0) { 681 | WRAP_AND_RETURN(unsigned long); 682 | } else if (strcmp(returnType, @encode(unsigned long long)) == 0) { 683 | WRAP_AND_RETURN(unsigned long long); 684 | } else if (strcmp(returnType, @encode(float)) == 0) { 685 | WRAP_AND_RETURN(float); 686 | } else if (strcmp(returnType, @encode(double)) == 0) { 687 | WRAP_AND_RETURN(double); 688 | } else if (strcmp(returnType, @encode(BOOL)) == 0) { 689 | WRAP_AND_RETURN(BOOL); 690 | } else if (strcmp(returnType, @encode(bool)) == 0) { 691 | WRAP_AND_RETURN(BOOL); 692 | } else if (strcmp(returnType, @encode(char *)) == 0) { 693 | WRAP_AND_RETURN(const char *); 694 | } else if (strcmp(returnType, @encode(char)) == 0) { 695 | WRAP_AND_RETURN(char); 696 | } else if (strcmp(returnType, @encode(void (^)(void))) == 0) { 697 | __unsafe_unretained id block = nil; 698 | [invocation getReturnValue:&block]; 699 | return block; 700 | } else { 701 | NSUInteger valueSize = 0; 702 | NSGetSizeAndAlignment(returnType, &valueSize, NULL); 703 | 704 | unsigned char valueBytes[valueSize]; 705 | [invocation getReturnValue:valueBytes]; 706 | 707 | return [NSValue valueWithBytes:valueBytes objCType:returnType]; 708 | } 709 | 710 | return nil; 711 | } 712 | 713 | @end 714 | 715 | @implementation NSDictionary (CTXMutableDeepCopy) 716 | 717 | - (NSMutableDictionary *)ctx_deepCopy 718 | { 719 | NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count]; 720 | 721 | NSArray *keys = [self allKeys]; 722 | 723 | for(id key in keys) { 724 | id oneValue = [self objectForKey:key]; 725 | id oneCopy = nil; 726 | 727 | if([oneValue conformsToProtocol:@protocol(NSCopying)]){ 728 | oneCopy = [oneValue copy]; 729 | } else { 730 | oneCopy = oneValue; 731 | } 732 | 733 | [returnDict setValue:oneCopy forKey:key]; 734 | } 735 | 736 | return returnDict; 737 | } 738 | 739 | - (NSMutableDictionary *)ctx_mutableDeepCopy 740 | { 741 | NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count]; 742 | 743 | NSArray *keys = [self allKeys]; 744 | 745 | for(id key in keys) { 746 | id oneValue = [self objectForKey:key]; 747 | id oneCopy = nil; 748 | 749 | if([oneValue respondsToSelector:@selector(ctx_mutableDeepCopy)]) { 750 | oneCopy = [oneValue ctx_mutableDeepCopy]; 751 | } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) { 752 | oneCopy = [oneValue mutableCopy]; 753 | } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){ 754 | oneCopy = [oneValue copy]; 755 | } else { 756 | oneCopy = oneValue; 757 | } 758 | 759 | [returnDict setValue:oneCopy forKey:key]; 760 | } 761 | 762 | return returnDict; 763 | } 764 | 765 | @end 766 | 767 | @implementation NSMutableDictionary (CTXSetSafeValueForKey) 768 | 769 | - (void)ctx_setSafeValue:(id)value forKeyPath:(NSString *)keyPath withOptions:(enum CTXPropertyMapperExportOption)options 770 | { 771 | if(value == nil) { 772 | if (options == CTXPropertyMapperExportOptionIncludeNullValue) { 773 | value = [NSNull null]; 774 | } else { 775 | return; 776 | } 777 | } 778 | 779 | [self setValue:value forKeyPath:keyPath]; 780 | } 781 | 782 | @end 783 | 784 | @implementation NSArray (CTXMutableDeepCopy) 785 | 786 | - (NSMutableArray *)ctx_deepCopy 787 | { 788 | NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count]; 789 | 790 | for(id oneValue in self) { 791 | id oneCopy = nil; 792 | 793 | if([oneValue conformsToProtocol:@protocol(NSCopying)]){ 794 | oneCopy = [oneValue copy]; 795 | } else { 796 | oneCopy = oneValue; 797 | } 798 | 799 | [returnArray addObject:oneCopy]; 800 | } 801 | 802 | return returnArray; 803 | } 804 | 805 | - (NSMutableArray *)ctx_mutableDeepCopy 806 | { 807 | NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count]; 808 | 809 | for(id oneValue in self) { 810 | id oneCopy = nil; 811 | 812 | if([oneValue respondsToSelector:@selector(ctx_mutableDeepCopy)]) { 813 | oneCopy = [oneValue ctx_mutableDeepCopy]; 814 | } else if([oneValue conformsToProtocol:@protocol(NSMutableCopying)]) { 815 | oneCopy = [oneValue mutableCopy]; 816 | } else if([oneValue conformsToProtocol:@protocol(NSCopying)]){ 817 | oneCopy = [oneValue copy]; 818 | } else { 819 | oneCopy = oneValue; 820 | } 821 | 822 | [returnArray addObject:oneCopy]; 823 | } 824 | 825 | return returnArray; 826 | } 827 | 828 | @end 829 | 830 | @implementation NSMutableArray (CTXSetSafeValueForKey) 831 | 832 | - (void)ctx_safeAddObject:(id)object 833 | { 834 | if(object == nil) { 835 | return; 836 | } 837 | 838 | [self addObject:object]; 839 | } 840 | 841 | @end 842 | --------------------------------------------------------------------------------