├── 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 |
--------------------------------------------------------------------------------