Skip to content

Commit

Permalink
Added a default mapping block for specific classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ipodishima committed Feb 29, 2016
1 parent 20c9afc commit 6420958
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 31 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.0.3 - Released Feb 29, 2015
- Added `WANSCodingStore` to the main header
- Added a way to register default mapping block for specific classes.
For example, you can now add a default mapping block to turn `strings` to `NSDate`

## 0.0.2 - Released Feb 26, 2015
- Exposed the store on WAMapper
- Added a new store: `WANSCodingStore`
Expand Down
9 changes: 9 additions & 0 deletions Files/WAMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

@import Foundation;
#import "WABlockMapping.h"

@class WAEntityMapping;
@protocol WAStoreProtocol;
Expand Down Expand Up @@ -44,6 +45,14 @@ typedef void (^WAMapperCompletionBlock)(NSArray *mappedObjects);
*/
- (void)mapFromRepresentation:(id)json mapping:(WAEntityMapping *)mapping completion:(WAMapperCompletionBlock)completion;

/**
* Add a default mapping block for a class. For example, you could have an API returning dates all with the same format. You can register the transformation once here.
*
* @param mappingBlock the mapping block called to transform the value
* @param destinationClass the destination class
*/
- (void)addDefaultMappingBlock:(WAMappingBlock)mappingBlock forDestinationClass:(Class)destinationClass;

@property (nonatomic, strong, readonly) id <WAStoreProtocol> store;

@end
41 changes: 37 additions & 4 deletions Files/WAMapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
@interface WAMapper ()

@property (nonatomic, strong) id <WAStoreProtocol> store;
@property (nonatomic, strong) NSMutableDictionary *defaultMappingBlocks;

@end

Expand Down Expand Up @@ -55,6 +56,17 @@ - (void)mapFromRepresentation:(id)json mapping:(WAEntityMapping *)mapping comple
completion(mappedObjects);
}

- (void)addDefaultMappingBlock:(WAMappingBlock)mappingBlock forDestinationClass:(Class)destinationClass {
WAMParameterAssert(destinationClass);
WAMParameterAssert(mappingBlock);

if (!self.defaultMappingBlocks) {
self.defaultMappingBlocks = [NSMutableDictionary dictionary];
}

self.defaultMappingBlocks[NSStringFromClass(destinationClass)] = mappingBlock;
}

#pragma mark - Private methods

- (NSArray *)_mapFromRepresentation:(id)json mapping:(WAEntityMapping *)mapping {
Expand Down Expand Up @@ -171,14 +183,26 @@ - (NSArray *)_applyMapping:(WAEntityMapping *)mapping onObject:(id)object withRe
}

WAPropertyMapping *propertyMapping = mapping.attributeMappings[key];
id valueAfterMappingBlock = finalValue;
if (finalValue) {
finalValue = propertyMapping.mappingBlock(finalValue);
valueAfterMappingBlock = propertyMapping.mappingBlock(finalValue);
}

if ([valueAfterMappingBlock isEqual:finalValue]) {
// This means that the property has no real transformation. Use global one
WAMappingBlock defaultMappingBlock = [self _defaultMappingBlockForDestinationClass:
NSClassFromString([WAPropertyTransformation propertyTypeStringRepresentationFromPropertyName:propertyMapping.destinationPropertyName
forObject:object])];

if (defaultMappingBlock) {
valueAfterMappingBlock = defaultMappingBlock(finalValue);
}
}

if ((!finalValue && [value isEqual:[NSNull null]])
if ((!valueAfterMappingBlock && [value isEqual:[NSNull null]])
||
finalValue) {
[object wa_setValueIfChanged:finalValue
valueAfterMappingBlock) {
[object wa_setValueIfChanged:valueAfterMappingBlock
forKey:propertyMapping.destinationPropertyName];
}
}
Expand Down Expand Up @@ -284,4 +308,13 @@ - (NSArray *)_applyMapping:(WAEntityMapping *)mapping onObject:(id)object withRe
return [relationShipObjects copy];
}

- (WAMappingBlock)_defaultMappingBlockForDestinationClass:(Class)destinationClass {
NSString *key = NSStringFromClass(destinationClass);
if (!key) {
return nil;
}

return self.defaultMappingBlocks[key];
}

@end
1 change: 1 addition & 0 deletions Files/WAPropertyTransformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
@interface WAPropertyTransformation : NSObject

+ (id)propertyValue:(id)initialValue fromPropertyName:(NSString *)propertyName forObject:(id)object;
+ (NSString *)propertyTypeStringRepresentationFromPropertyName:(NSString *)propertyName forObject:(id)object;
+ (BOOL)isClassACollection:(Class)class;
+ (BOOL)isClassAMutableCollection:(Class)class;
+ (id)convertObject:(id)object toClass:(Class)destinationClass;
Expand Down
9 changes: 9 additions & 0 deletions Files/WAReverseMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

@import Foundation;
#import "WABlockMapping.h"

@class WAEntityMapping;

Expand All @@ -28,4 +29,12 @@ typedef BOOL (^WAReverseMapperShouldMapRelationshipBlock)(NSString *sourceRelati
*/
- (NSArray <NSDictionary *>*)reverseMapObjects:(NSArray *)objects fromMapping:(WAEntityMapping *)mapping shouldMapRelationship:(WAReverseMapperShouldMapRelationshipBlock)shouldMapRelationshipBlock;

/**
* Add a reverse default mapping block for a class. For example, you could have an API returning dates all with the same format. You can register the transformation once here.
*
* @param reverseMappingBlock the reverse mapping block called to transform the value
* @param destinationClass the destination class
*/
- (void)addReverseDefaultMappingBlock:(WAMappingBlock)reverseMappingBlock forDestinationClass:(Class)destinationClass;

@end
42 changes: 39 additions & 3 deletions Files/WAReverseMapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

#import "NSMutableDictionary+WASubDictionary.h"

@interface WAReverseMapper ()

@property (nonatomic, strong) NSMutableDictionary *defaultReverseMappingBlocks;

@end

@implementation WAReverseMapper

- (NSArray *)reverseMapObjects:(NSArray *)objects fromMapping:(WAEntityMapping *)mapping shouldMapRelationship:(WAReverseMapperShouldMapRelationshipBlock)shouldMapRelationshipBlock {
Expand Down Expand Up @@ -51,6 +57,17 @@ - (NSArray *)reverseMapObjects:(NSArray *)objects fromMapping:(WAEntityMapping *
return [allObjectsAsDictionaries copy];
}

- (void)addReverseDefaultMappingBlock:(WAMappingBlock)reverseMappingBlock forDestinationClass:(Class)destinationClass {
WAMParameterAssert(destinationClass);
WAMParameterAssert(reverseMappingBlock);

if (!self.defaultReverseMappingBlocks) {
self.defaultReverseMappingBlocks = [NSMutableDictionary dictionary];
}

self.defaultReverseMappingBlocks[NSStringFromClass(destinationClass)] = reverseMappingBlock;
}

#pragma mark - Private

- (NSString *)_uniqueKeyForObject:(id)object mapping:(WAEntityMapping *)mapping {
Expand Down Expand Up @@ -85,10 +102,20 @@ - (NSDictionary *)_reverseMapObject:(id)object withMapping:(WAEntityMapping *)ma
for (NSString *key in [reverseMapping allKeys]) {
WAPropertyMapping *propertyMapping = reverseMapping[key];

id value = [object valueForKeyPath:key];
value = propertyMapping.reverseMappingBlock(value);
id value = [object valueForKeyPath:key];
id finalValue = propertyMapping.reverseMappingBlock(value);

[objectAsDictionary wa_setObject:value ?: [NSNull null] byCreatingDictionariesForKeyPath:propertyMapping.sourcePropertyName];
if ([finalValue isEqual:value]) {
WAMappingBlock defaultReverseMappingBlock = [self _defaultReverseMappingBlockForDestinationClass:
NSClassFromString([WAPropertyTransformation propertyTypeStringRepresentationFromPropertyName:propertyMapping.destinationPropertyName
forObject:object])];

if (defaultReverseMappingBlock) {
finalValue = defaultReverseMappingBlock(value);
}
}

[objectAsDictionary wa_setObject:finalValue ?: [NSNull null] byCreatingDictionariesForKeyPath:propertyMapping.sourcePropertyName];
}

// Map the relation ships
Expand Down Expand Up @@ -176,4 +203,13 @@ - (NSDictionary *)_removeRelationshipsValues:(NSDictionary *)originalRepresentat
return [finalRepresentation copy];
}

- (WAMappingBlock)_defaultReverseMappingBlockForDestinationClass:(Class)destinationClass {
NSString *key = NSStringFromClass(destinationClass);
if (!key) {
return nil;
}

return self.defaultReverseMappingBlocks[key];
}

@end
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,55 @@ json = [reverseMapper reverseMapObjects:enterprises
fromMapping:enterpriseMapping
shouldMapRelationship:nil];
```
# Default mappings
If you have a server which returns all dates within the same format, then you can ask the mapper or the reverse mapper once to transform the value.
Instead of writing
```objc
[enterpriseMapping addAttributeMappingsFromDictionary:@{
@"id": @"itemID",
@"name": @"name",
@"address.street_number": @"streetNumber"
}];
// Map custom values. Here an `NSDate` from a string using an `NSDateTransformer`
[enterpriseMapping addMappingFromSourceProperty:@"creation_date"
toDestinationProperty:@"creationDate"
withBlock:^id(id value) {
return [dateFormatter dateFromString:value];
}
reverseBlock:^id(id value) {
return [dateFormatter stringFromDate:value];
}];
```

You would write

```objc
[enterpriseMapping addAttributeMappingsFromDictionary:@{
@"id": @"itemID",
@"name": @"name",
@"address.street_number": @"streetNumber",
@"creation_date": @"creationDate"
}];


id(^toDateMappingBlock)(id ) = ^id(id value) {
if ([value isKindOfClass:[NSString class]]) {
return [dateFormatter dateFromString:value];
}

return value;
};

[mapper addDefaultMappingBlock:toDateMappingBlock
forDestinationClass:[NSDate class]];
```
The same thing happens to the reverse mapper. Note that if you provide a custom mapping on an `NSDate` object for a specific property (like a date with only the year), you can add the property to the entity mapping which will override the default behavior for this specific property.
# Side notes
## TODOs
Expand Down
4 changes: 2 additions & 2 deletions WAMapping.podspec
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Pod::Spec.new do |s|
s.name = "WAMapping"
s.version = "0.0.2"
s.version = "0.0.3"
s.summary = "WAMapping is a library which turns dictionary to object and vice versa. Designed for speed!"
s.homepage = "https://github.com/Wasappli/WAMapping"
s.license = { :type => "MIT", :file => "LICENSE" }
s.author = { "Marian Paul" => "marian@wasapp.li" }
s.platform = :ios, "7.0"
s.source = { :git => "https://github.com/Wasappli/WAMapping.git", :tag => "0.0.2" }
s.source = { :git => "https://github.com/Wasappli/WAMapping.git", :tag => "0.0.3" }
s.source_files = "Files/*.{h,m}"
s.requires_arc = true
s.frameworks = "CoreData"
Expand Down
3 changes: 2 additions & 1 deletion WAMappingTests/ClassicEnterpriseWithOneEmployee.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
},
"employees": [{
"id": 1,
"first_name": "Marian"
"first_name": "Marian",
"birth_date": "1987"
}]
}
1 change: 1 addition & 0 deletions WAMappingTests/Employee.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
@property (nonatomic, strong) NSNumber *itemID;
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) Enterprise *enterprise;
@property (nonatomic, strong) NSDate *birthDate;

@end
1 change: 1 addition & 0 deletions WAMappingTests/EmployeeCD+CoreDataProperties.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nullable, nonatomic, retain) NSNumber *itemID;
@property (nullable, nonatomic, retain) NSString *firstName;
@property (nullable, nonatomic, retain) EnterpriseCD *enterprise;
@property (nullable, nonatomic, retain) NSDate *birthDate;

@end

Expand Down
1 change: 1 addition & 0 deletions WAMappingTests/EmployeeCD+CoreDataProperties.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ @implementation EmployeeCD (CoreDataProperties)
@dynamic itemID;
@dynamic firstName;
@dynamic enterprise;
@dynamic birthDate;

@end
5 changes: 3 additions & 2 deletions WAMappingTests/Model.xcdatamodeld/Model.xcdatamodel/contents
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9525" systemVersion="15B42" minimumToolsVersion="Xcode 7.0">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9525" systemVersion="14F27" minimumToolsVersion="Xcode 7.0">
<entity name="Employee" representedClassName="EmployeeCD" syncable="YES">
<attribute name="birthDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="firstName" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="itemID" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
<relationship name="enterprise" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Enterprise" inverseName="employees" inverseEntity="Enterprise" syncable="YES"/>
Expand All @@ -15,7 +16,7 @@
<relationship name="orderedEmployees" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="Employee" syncable="YES"/>
</entity>
<elements>
<element name="Employee" positionX="-63" positionY="-18" width="128" height="90"/>
<element name="Employee" positionX="-63" positionY="-18" width="128" height="105"/>
<element name="Enterprise" positionX="-54" positionY="9" width="128" height="150"/>
</elements>
</model>
Loading

0 comments on commit 6420958

Please sign in to comment.