TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Apr 2015 |
Maintained by Paul Taykalo.
Object mapping for parsing dictionaries into object
Using CocoaPods
pod 'SFMapping', :git => 'https://github.com/stanfy/SFObjectMapping.git', :tag => '0.0.3'
It's easy to parse JSON into dictionary using JSONKit, but how to parse dictionary into model?
Easy! Like this:
SMBaseObject * parsedObject = [SFMappingCore instanceOfClass:[SMBaseObject class] fromObject:dictionary];
All you need to do is to create SMBaseObject and write mapping from dictionary key path to property name.
in SFBaseObject.h
:
@interface SFBaseObject : NSObject
@property (nonatomic, strong) NSString * ID;
@end
somewhere in your code where it's more appropriate for you setup mapping for SFBaseObject
in format property name
- key path
:
#import "SFMapping.h"
#import "NSObject+SFMapping.h"
+ (void)setupMappingsForModelObjects {
[SFBaseObject setSFMappingInfo:
[SFMapping property:@"ID" toKeyPath:@"id"],
nil];
}
Lets look on mapping for different simple properties:
@property (nonatomic, strong) NSString * pString;
@property (nonatomic, strong) NSNumber * pNumber;
@property (nonatomic, assign) BOOL pBoolean;
Mapping:
#import "SFMapping.h"
#import "NSObject+SFMapping.h"
+ (void)setupMappingsForModelObjects {
[SFBaseObject setSFMappingInfo:
[SFMapping property:@"pString" toKeyPath:@"someStringFromDictionary"],
[SFMapping property:@"pNumber" toKeyPath:@"someNumberFromDictionary"],
[SFMapping property:@"pBoolean" toKeyPath:@"someBooleanFromDictionary"],
nil];
}
If SFObjectWithArray
has array of SFArrayItem
objects:
@interface SFObjectWithArray : NSObject
@property (nonatomic, strong) NSMutableArray * mutableArray;
@property (nonatomic, strong) NSMutableArray * immutableArray;
@end
Mapping:
#import "SFMapping.h"
#import "NSObject+SFMapping.h"
+ (void)setupMappingsForModelObjects {
[SFObjectWithArray setSFMappingInfo:
[SFMapping collection:@"mutableArray" classString:@"NSMutableArray"
itemClass:@"SFArrayItem" toKeyPath:@"someMutableArrayFromDictionary"],
[SFMapping collection:@"immutableArray" classString:@"NSArray"
itemClass:@"SFArrayItem" toKeyPath:@"someArrayFromDictionary"],
nil];
}
typedef NS_ENUM(NSInteger , SFUserGender) {
SFUserGenderFemale = 0,
SFUserGenderMale = 1
};
typedef NS_ENUM(NSInteger , SFUserType) {
SFUserTypeNormal= 0,
SFUserTypePremium = 1
};
@interface SFUser : NSObject
@property(nonatomic, assign) SFUserGender gender;
@property(nonatomic, assign) SFUserType userType;
@property(nonatomic, copy) NSString * name;
@end
There's two suggested variants how you can map properties.
First one is to register global mapper with fake classname
Second one is to use custom mapper right in the mapping definition
Mapping #1. Using global mapper:
/*
Register global mapper for all types of SFUserGender
*/
NSDictionary *userGenderDictionary =
@{@"female" : @(SFUserGenderFemale),
@"male" : @(SFUserGenderMale)
};
// Registering mapper for fake class named SFUserGender
[SFMappingCore registerMapper:
[SFBlockBasedMapper mapperWithValueTransformBlock:^id(SFMapping *mapping, id value) {
return value ? userGenderDictionary[value] : nil;
}] forClass:@"SFUserGender"];
[SFUser setSFMappingInfo:
// Here we're specifying this property "class", since we registered global mapper for this class previously
[SFMapping property:@"gender" classString:@"SFUserGender"], // gender in JSON
// ....
nil
];
Mapping #2. Using cutom mapper:
NSDictionary *userTypeDictionary =
@{@"premium" : @(SFUserTypePremium),
@"normal" : @(SFUserTypeNormal)
};
[SFUser setSFMappingInfo:
// ,,,
// Here we're specifying local mapper which is used only for this mapping
[SFMapping property:@"userType" keyPath:@"type" valueBlock:^id(SFMapping *mapping, id value) {
return value ? userTypeDictionary[value] : nil;;
}],
nil
];
If object has reference to SFAnotherObject
:
@property (nonatomic, strong) SFAnotherObject * anotherObject;
Mapping:
+ (void)setupMappingsForModelObjects {
[SFBaseObject setSFMappingInfo:
[SFMapping property:@"anotherObject" toKeyPath:@"someObjectKeyPath"],
nil];
}
In this case, mapper will try to created SFAnotherObject
and apply mappings to it, by provided rules.
If there's no mappings found for SFAnotherObject
then, empty object will be created [SFAnotherObject new]
In case, if mapping found, then those will be applied correctly
For parsing dates we're suggesting to use SFDateMapper
class
You can use from predefined mappers with some general date formats:
/*
Format : EEE, dd MMM yyyy HH:mm:ss z
locale : en_US_POSIX
Timezone: UTC
*/
+ (SFDateMapper *)rfc2882DateTimeMapper;
/*
Format : yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'
locale : en_US_POSIX
Timezone: UTC
*/
+ (SFDateMapper *)rfc3339DateTimeMapper;
/*
Format : yyyy-MM-dd'T'HH:mm:ssZZZZZ
locale : en_US_POSIX
Timezone: Defined by format
*/
+ (SFDateMapper *)iso8601DateTimeMapper;
or, if you want to create your own, you can create your own mapper for your own, proprietary date format.
// Mapper for dates in timestamp(seconds since 1970) format
+ (id<SFMapper>)timestampMapper {
static dispatch_once_t once;
static id<SFMapper> mapper;
dispatch_once(&once, ^{
mapper = [SFBlockBasedMapper mapperWithValueTransformBlock:^id(SFMapping *mapping, id value) {
return [NSDate dateWithTimeIntervalSince1970:[value doubleValue]];
}];
});
return mapper;
}
// Mapper for dates in timestamp("/Date(1302055696487)/") format
+ (id<SFMapper>)dotNetTimestampMapper {
static dispatch_once_t once;
static id<SFMapper> mapper;
dispatch_once(&once, ^{
static NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"/Date()/"];
mapper = [SFBlockBasedMapper mapperWithValueTransformBlock:^id(SFMapping *mapping, id value) {
NSString *stringWithTimeStamp = [value stringByTrimmingCharactersInSet:characterSet];
return [NSDate dateWithTimeIntervalSince1970:[stringWithTimeStamp doubleValue]];
}];
});
return mapper;
}
Once you have date mapper, whether it's SFDateMapper
subclass, or some custom implementation, you, generally have two ways of using it - registering it's as global mapper for NSDate
class, or setting it as custom mapper to mappings you want:
// Since we know that all dates will be in rfc2882 format,
// We just set global mapper on NSDate class
[SFMappingCore registerMapper:[SFDateMapper rfc2882DateTimeMapper] forClass:@"NSDate"];
[SFActivity setSFMappingInfo:
// Mapping date by using global date mapper
[SFMapping property:@"date"],
// Just in case if we need to map this property differently, we can use custom mapper
//[[SFMapping property:@"date"] applyCustomMapper:[SFDateMapper rfc3339DateTimeMapper]],
nil
];
}