FastEasyMapping
A tool for fast serializing & deserializing of JSON
Install / Use
/learn @Yalantis/FastEasyMappingREADME
FastEasyMapping
Note
This is a fork of EasyMapping, a flexible and easy framework for JSON mapping.
Overview
It turns out, that almost all popular libraries for JSON mapping are SLOW. The main reason for that is multiple trips to database during the lookup of existing objects. We decided to take an already existing flexible solution (i.e. EasyMapping) and improve its overall performance.
<p align="center" > <img src="https://raw.githubusercontent.com/Yalantis/FastEasyMapping/efabb88b0831c7ece88e728b9665edc4d3af5b1f/Assets/performance.png" alt="FastEasyMapping" title="FastEasyMapping"> </p>Benchmark done on June/2014. Results may be outdated (EasyMapping performs nearly identical nowadays).
Requirements
Platform | Min Deployment Target :---: | :---: iOS | 8.0 macOS | 10.10 tvOS | 9.0 watchOS | 2.0
Build using Xcode 8.3.2+
Installation
CocoaPods
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapods
To integrate FastEasyMapping into your Xcode project using CocoaPods, specify it in your Podfile:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!
target '<Your Target Name>' do
pod 'FastEasyMapping', '~> 1.2'
end
Then, run the following command:
$ pod install
Carthage
Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
You can install Carthage with Homebrew using the following command:
$ brew update
$ brew install carthage
To integrate FastEasyMappingRealm into your Xcode project using Carthage, specify it in your Cartfile:
github "Yalantis/FastEasyMapping" ~> 1.2
Run carthage update to build the framework and drag the built FastEasyMapping.framework into your Xcode project.
Usage
Deserialization
Today NSObject and NSManagedObject mapping are supported out of the box. Lets take a look at how a basic mapping looks like: For example, we have JSON:
{
"name": "Lucas",
"user_email": "lucastoc@gmail.com",
"car": {
"model": "i30",
"year": "2013"
},
"phones": [
{
"ddi": "55",
"ddd": "85",
"number": "1111-1111"
},
{
"ddi": "55",
"ddd": "11",
"number": "2222-222"
}
]
}
and corresponding CoreData-generated classes:
@interface Person : NSManagedObject
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *email;
@property (nonatomic, retain) Car *car;
@property (nonatomic, retain) NSSet *phones;
@end
@interface Car : NSManagedObject
@property (nonatomic, retain) NSString *model;
@property (nonatomic, retain) NSString *year;
@property (nonatomic, retain) Person *person;
@end
@interface Phone : NSManagedObject
@property (nonatomic, retain) NSString *ddi;
@property (nonatomic, retain) NSString *ddd;
@property (nonatomic, retain) NSString *number;
@property (nonatomic, retain) Person *person;
@end
In order to map JSON to Object and vice versa we have to describe the mapping rules:
@implementation Person (Mapping)
+ (FEMMapping *)defaultMapping {
FEMMapping *mapping = [[FEMMapping alloc] initWithEntityName:@"Person"];
[mapping addAttributesFromArray:@[@"name"]];
[mapping addAttributesFromDictionary:@{@"email": @"user_email"}];
[mapping addRelationshipMapping:[Car defaultMapping] forProperty:@"car" keyPath:@"car"];
[mapping addToManyRelationshipMapping:[Phone defaultMapping] forProperty:@"phones" keyPath:@"phones"];
return mapping;
}
@end
@implementation Car (Mapping)
+ (FEMMapping *)defaultMapping {
FEMMapping *mapping = [[FEMMapping alloc] initWithEntityName:@"Car"];
[mapping addAttributesFromArray:@[@"model", @"year"]];
return mapping;
}
@end
@implementation Phone (Mapping)
+ (FEMMapping *)defaultMapping {
FEMMapping *mapping = [[FEMMapping alloc] initWithEntityName:@"Phone"];
[mapping addAttributesFromArray:@[@"number", @"ddd", @"ddi"]];
return mapping;
}
@end
Now we can deserialize JSON to Object easily:
FEMMapping *mapping = [Person defaultMapping];
Person *person = [FEMDeserializer objectFromRepresentation:json mapping:mapping context:managedObjectContext];
Or collection of Objects:
NSArray *persons = [FEMDeserializer collectionFromRepresentation:json mapping:mapping context:managedObjectContext];
Or even update an Object:
[FEMDeserializer fillObject:person fromRepresentation:json mapping:mapping];
Serialization
Also we can serialize an Object to JSON using the mapping defined above:
FEMMapping *mapping = [Person defaultMapping];
Person *person = ...;
NSDictionary *json = [FEMSerializer serializeObject:person usingMapping:mapping];
Or collection to JSON:
FEMMapping *mapping = [Person defaultMapping];
NSArray *persons = ...;
NSArray *json = [FEMSerializer serializeCollection:persons usingMapping:mapping];
Mapping
FEMAttribute
FEMAttribute is a core class of FEM. Briefly it is a description of relationship between the Object's property and the JSON's keyPath. Also it encapsulates knowledge of how the value needs to be mapped from Object to JSON and back via blocks.
typedef __nullable id (^FEMMapBlock)(id value __nonnull);
@interface FEMAttribute : NSObject <FEMProperty>
@property (nonatomic, copy, nonnull) NSString *property;
@property (nonatomic, copy, nullable) NSString *keyPath;
- (nonnull instancetype)initWithProperty:(nonnull NSString *)property keyPath:(nullable NSString *)keyPath map:(nullable FEMMapBlock)map reverseMap:(nullable FEMMapBlock)reverseMap;
- (nullable id)mapValue:(nullable id)value;
- (nullable id)reverseMapValue:(nullable id)value;
@end
Alongside with property and keyPath value you can pass mapping blocks that allow to describe completely custom mappings.
Examples:
Mapping of value with the same keys and type:
FEMAttribute *attribute = [FEMAttribute mappingOfProperty:@"url"];
// or
FEMAttribute *attribute = [[FEMAttribute alloc] initWithProperty:@"url" keyPath:@"url" map:NULL reverseMap:NULL];
Mapping of value with different keys and the same type:
FEMAttribute *attribute = [FEMAttribute mappingOfProperty:@"urlString" toKeyPath:@"URL"];
// or
FEMAttribute *attribute = [[FEMAttribute alloc] initWithProperty:@"urlString" keyPath:@"URL" map:NULL reverseMap:NULL];
Mapping of different types:
Quite often value type in JSON needs to be converted to more useful internal representation. For example HEX to UIColor, String to NSURL, Integer to enum and so on. For this purpose you can use map and reverseMap properties. For example lets describe attribute that maps String to NSDate using NSDateFormatter:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
FEMAttribute *attribute = [[FEMAttribute alloc] initWithProperty:@"updateDate" keyPath:@"timestamp" map:^id(id value) {
if ([value isKindOfClass:[NSString class]]) {
return [formatter dateFromString:value];
}
return nil;
} reverseMap:^id(id value) {
return [formatter stringFromDate:value];
}];
First of all we've defined NSDateFormatter that fits our requirements. Next step is to define Attribute instance with correct mapping. Briefly map block is invoked during deserialization (JSON to Object) while reverseMap is used for serialization process. Both are quite stratforward with but with few gotchas:
mapcan receiveNSNullinstance. This is a valid case for
