Skip to content
This repository has been archived by the owner on Sep 5, 2023. It is now read-only.

Commit

Permalink
Adding enum support (value mapping)
Browse files Browse the repository at this point in the history
  • Loading branch information
vilanovi committed Mar 11, 2015
1 parent ee4c896 commit 0c5ee71
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 24 deletions.
4 changes: 2 additions & 2 deletions Motis.podspec.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "Motis",
"version": "1.0.2",
"version": "1.1.0",
"summary": "Easy JSON to NSObject mapping",
"description": "Map and set your JSON objects to objective-C objects using Cocoa's KeyValueCoding. This category sets a minimalist set of methods to map the JSON keys into class properties and set your JSON objects into NSObjects subclasses.\n\t\t \nFor a basic overview of how Motis works checkout the Mobile Jazz blog post: http://blog.mobilejazz.cat/ios-using-kvc-to-parse-json\n\nSimple & Powerful.",
"homepage": "https://github.com/mobilejazz/Motis",
Expand All @@ -14,7 +14,7 @@
"social_media_url": "http://twitter.com/mobilejazzcat",
"source": {
"git": "https://github.com/mobilejazz/Motis.git",
"tag": "1.0.2"
"tag": "1.1.0"
},
"source_files": [
"Motis.h",
Expand Down
15 changes: 14 additions & 1 deletion NSObject+Motis.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
**/
#define mts_key(name) NSStringFromSelector(@selector(name))

/**
* Use this value to define a default value in the value mapping.
**/
#define MTSDefaultValue [NSNull null]

/* *************************************************************************************************************************************** *
* Motis Methods
* *************************************************************************************************************************************** */
Expand Down Expand Up @@ -194,6 +199,14 @@
* @name Automatic Validation
** ---------------------------------------------- **/

/**
* Return a mapping for enum types.
* @param key The key of the property.
* @return The dictionary with the enumeration mapping for the given key.
* @discussion Using this method it is possible to convert the value received via the mapping.
**/
+ (NSDictionary*)mts_valueMappingForKey:(NSString*)key;

/**
* Return a mapping between the array property name to the contained object class type.
* For example: @{@"myArrayPropertyName": User.class, ... };
Expand Down Expand Up @@ -283,6 +296,6 @@
* @param key The key of the attribute.
* @discussion This can be useful when using Motis with Core Data, because Core Data will flag `NSManagedObject` instances as updated, triggering database work, even if no properties have meaningfully changed.
**/
- (BOOL)mts_checkValueOfKeyForEqualityBeforeAssignment:(NSString *)key;
- (BOOL)mts_checkValueEqualityBeforeAssignmentForKey:(NSString *)key;

@end
116 changes: 112 additions & 4 deletions NSObject+Motis.m
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ @interface NSObject (Motis_Private)
**/
+ (NSDictionary*)mts_cachedMapping;

/**
* Collects all the value mappings from each subclass layer.
* @return The value mapping for the given key.
**/
+ (NSDictionary *)mts_cachedValueMappingForKey:(NSString*)key;

/**
* Collects all the array class mappings from each subclass layer.
* @return The Motis Array Class Mapping.
Expand Down Expand Up @@ -228,12 +234,25 @@ - (void)mts_setValue:(id)value forKey:(NSString *)key

// Automatic validation only if the value has not been manually validated
if (originalValue == value && validated)
validated = [self mts_validateAutomaticallyValue:&value forKey:mappedKey];
{
id mappedValue = [self mts_mapValue:value forKey:mappedKey];

if (mappedValue != value)
value = mappedValue;
else
validated = [self mts_validateAutomaticallyValue:&value forKey:mappedKey];
}

if (validated)
{
if (![self mts_checkValueOfKeyForEqualityBeforeAssignment:mappedKey]
|| ![[self mts_valueForKey:key] isEqual:value])
if ([self mts_checkValueEqualityBeforeAssignmentForKey:mappedKey])
{
if (![[self mts_valueForKey:key] isEqual:value])
{
[self setValue:value forKey:mappedKey];
}
}
else
{
[self setValue:value forKey:mappedKey];
}
Expand All @@ -242,6 +261,47 @@ - (void)mts_setValue:(id)value forKey:(NSString *)key
[self mts_invalidValue:value forKey:mappedKey error:error];
}

- (id)mts_mapValue:(id)value forKey:(NSString*)key
{
NSDictionary *valueMapping = [self.class mts_cachedValueMappingForKey:key];

if (valueMapping.count > 0)
{
id mappedValue = valueMapping[value];

// Direct mapping
if (mappedValue)
return mappedValue;

// Conversion to string
if ([value isKindOfClass:NSNumber.class])
{
mappedValue = valueMapping[[value stringValue]];
if (mappedValue)
return mappedValue;
}

// Conversion to number
if ([value isKindOfClass:NSString.class])
{
mappedValue = valueMapping[@([value floatValue])];
if (mappedValue)
return mappedValue;

mappedValue = valueMapping[@([value integerValue])];
if (mappedValue)
return mappedValue;
}

// NotFound value
mappedValue = valueMapping[MTSDefaultValue];
if (mappedValue)
return mappedValue;
}

return value;
}

- (void)mts_setValuesForKeysWithDictionary:(NSDictionary *)keyedValues
{
for (NSString *key in keyedValues)
Expand Down Expand Up @@ -374,6 +434,12 @@ + (BOOL)mts_shouldSetUndefinedKeys
return mapping.count == 0;
}

+ (NSDictionary *)mts_valueMappingForKey:(NSString *)key
{
// Subclasses might override.
return nil;
}

+ (NSDictionary*)mts_arrayClassMapping
{
// Subclasses might override.
Expand Down Expand Up @@ -427,7 +493,7 @@ - (void)mts_invalidValue:(id)value forArrayKey:(NSString *)key error:(NSError*)e
MJLog(@"Item for ArrayKey <%@> is not valid in class %@. Error: %@", key, [self.class description], error);
}

- (BOOL)mts_checkValueOfKeyForEqualityBeforeAssignment:(NSString *)key
- (BOOL)mts_checkValueEqualityBeforeAssignmentForKey:(NSString *)key
{
// Subclasses might override.
return NO;
Expand Down Expand Up @@ -490,6 +556,48 @@ + (NSDictionary*)mts_cachedMapping
}
}

+ (NSDictionary*)mts_cachedValueMappingForKey:(NSString*)key
{
static NSMutableDictionary *valueMappings = nil;
static dispatch_once_t onceToken1;
dispatch_once(&onceToken1, ^{
valueMappings = [NSMutableDictionary dictionary];
});

@synchronized(valueMappings)
{
NSString *className = stringFromClass(self);
NSMutableDictionary *classValueMappings = valueMappings[className];

if (!classValueMappings)
{
classValueMappings = [NSMutableDictionary dictionary];
valueMappings[className] = classValueMappings;
}

NSMutableDictionary *valueMapping = classValueMappings[key];

if (!valueMapping)
{
Class superClass = [self superclass];

NSMutableDictionary *dictionary = nil;

if ([superClass isSubclassOfClass:NSObject.class])
dictionary = [[superClass mts_cachedValueMappingForKey:key] mutableCopy];
else
dictionary = [NSMutableDictionary dictionary];

[dictionary addEntriesFromDictionary:[self mts_valueMappingForKey:key]];

valueMapping = [dictionary copy];
classValueMappings[key] = valueMapping;
}

return valueMapping;
}
}

+ (NSDictionary*)mts_cachedArrayClassMapping
{
static NSMutableDictionary *arrayClassMappings = nil;
Expand Down
30 changes: 30 additions & 0 deletions SampleProject/Motis Tests/MJValidationTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,36 @@ - (void)testStringToIdProtocol
XCTAssertEqualObjects(object.idProtocolField, string, @"Failed to map string value");
}

#pragma mark - ENUMS

#pragma mark Unsigned Enum

- (void)testUnsignedEnum
{
NSArray *array = @[@"zero",@"one",@"two",@"three",[NSNull null],@"four"];
NSArray *results = @[@(MJUnsignedEnumZero),@(MJUnsignedEnumOne),@(MJUnsignedEnumTwo),@(MJUnsignedEnumThree),@(MJUnsignedEnumOne),@(MJUnsignedEnumOne)];

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
MJMotisObject *object = [MJMotisObject new];
[object mts_setValue:obj forKey:@"unsigned_enum"];

XCTAssertEqual(object.unsignedEnum, [results[idx] integerValue], @"Failed to map unsigned enum value");
}];
}

- (void)testSignedEnum
{
NSArray *array = @[@"zero",@"one",@"two",@"three",[NSNull null],@"four"];
NSArray *results = @[@(MJSignedEnumZero),@(MJSignedEnumOne),@(MJSignedEnumTwo),@(MJSignedEnumThree),@(MJSignedEnumTwo),@(MJSignedEnumTwo)];

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
MJMotisObject *object = [MJMotisObject new];
[object mts_setValue:obj forKey:@"signed_enum"];

XCTAssertEqual(object.signedEnum, [results[idx] integerValue], @"Failed to map unsigned enum value");
}];
}

#pragma mark - FROM DICTIONARY

// ------------------------------------------------------------------------------------------------------------------------ //
Expand Down
27 changes: 27 additions & 0 deletions SampleProject/Motis Tests/Validation Test/MJMotisObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ + (NSDictionary*)mts_mapping
@"number_array": mts_key(numbersArray),
@"url_array": mts_key(urlsArray),
@"date_array": mts_key(datesArray),

@"unsigned_enum": mts_key(unsignedEnum),
@"signed_enum": mts_key(signedEnum),
};
}

Expand All @@ -55,6 +58,30 @@ + (NSDictionary*)mts_arrayClassMapping
};
}

+ (NSDictionary *)mts_valueMappingForKey:(NSString *)key
{
if ([key isEqualToString:mts_key(unsignedEnum)])
{
return @{@"zero": @(MJUnsignedEnumZero),
@"one": @(MJUnsignedEnumOne),
@"two": @(MJUnsignedEnumTwo),
@"three": @(MJUnsignedEnumThree),
MTSDefaultValue: @(MJUnsignedEnumOne),
};
}
else if ([key isEqualToString:mts_key(signedEnum)])
{
return @{@"zero": @(MJSignedEnumZero),
@"one": @(MJSignedEnumOne),
@"two": @(MJSignedEnumTwo),
@"three": @(MJSignedEnumThree),
MTSDefaultValue: @(MJSignedEnumTwo),
};
}

return nil;
}

#pragma mark Property Nillification

- (void)setNilValueForKey:(NSString *)key
Expand Down
19 changes: 19 additions & 0 deletions SampleProject/Motis Tests/Validation Test/MJTestObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@

@class MJMotisObject;

typedef NS_ENUM(NSUInteger, MJUnsignedEnum)
{
MJUnsignedEnumZero,
MJUnsignedEnumOne,
MJUnsignedEnumTwo,
MJUnsignedEnumThree
};

typedef NS_ENUM(NSInteger, MJSignedEnum)
{
MJSignedEnumZero,
MJSignedEnumOne,
MJSignedEnumTwo,
MJSignedEnumThree
};

@interface MJTestObject : NSObject

@property (nonatomic, assign) NSInteger integerField;
Expand Down Expand Up @@ -40,4 +56,7 @@
@property (nonatomic, strong) NSArray *urlsArray;
@property (nonatomic, strong) NSArray *datesArray;

@property (nonatomic, assign) MJUnsignedEnum unsignedEnum;
@property (nonatomic, assign) MJSignedEnum signedEnum;

@end
33 changes: 16 additions & 17 deletions SampleProject/Motis/MJAppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,7 @@ @implementation MJAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self testMotis];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self testMotis];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self testMotis];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self testMotis];
});
[self testMotis];

UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.rootViewController = [[UIViewController alloc] init];
Expand Down Expand Up @@ -67,19 +56,27 @@ - (void)testMotis
@"likes_count": [NSNull null],
@"uploader":@{@"user_name":@"Joan",
@"user_id": @7,
@"followers": @209
@"followers": @209,
@"user_gender": @"male",
@"awesomeness_level": @3,
},
@"users_cast":@[@{@"user_name":@"Stefan",
@"user_id": @19,
@"followers": @1209
@"followers": @1209,
@"user_gender": @"male",
@"awesomeness_level": @1,
},
@{@"user_name":@"Hermes",
@"user_id": @23,
@"followers": @1455
@"followers": @1455,
@"user_gender": @"male",
@"awesomeness_level": @2,
},
@{@"user_name":@"Jordi",
@{@"user_name":@"Martha",
@"user_id": @14,
@"followers": @452
@"followers": @452,
@"user_gender": @"female",
@"awesomeness_level": @3,
},
],
@"privateVideoKey": @(1234), // <-- simulating an unexpected attribute in the JSON
Expand Down Expand Up @@ -108,6 +105,8 @@ - (void)testMotis

NSLog(@"AFTER parsing: %@", video.mts_extendedObjectDescription);
NSLog(@"video.privateVideoKey: %@",[video.privateVideoKey description]);

NSLog(@"Video.uploader: %@", video.uploader.mts_extendedObjectDescription);
}

@end
Loading

0 comments on commit 0c5ee71

Please sign in to comment.