EasyMapping, or JSON Travel
Computer programs are the most complex things that humans make. It is also the nature of software to be extensively modified over its productive life. If we can read and understand it, then we can hope to modify and improve it.
Douglas Crockford, author of the JSON specification
JSON is a bridge between two worlds: the world of web services and the world of client applications. However, the bridge is not so perfect that the data exist in one format. So far, we are always forced to convert information into a representation of the language we are working with for the architecture of the application we are writing. In order for such a transformation to be successful, it must first of all be simple.
There are many ways to turn JSON into Objective-C objects, but many of them have their drawbacks that prevent them from working. There is a famous and beloved by many RestKitHowever, it, unfortunately, only works efficiently with the perfect REST API. A step to the side - and you will hammer in nails with a microscope, not understanding why you need to write such complex structures for fairly simple things. There is a solution from GitHub developers - Mantle , but with it you will be forced to inherit from the Mantle base class and constantly use NSValueTransformer - not the most popular technology in iOS / Mac OS development.
I want to talk about the framework that was recently found on the expanses of GitHub, and which allows you to easily and beautifully convert JSON to Objective-C objects - EasyMapping .
If you are interested, welcome to kat!
Task
Take for example the following JSON:
{
"name": "Lucas",
"email": "notexisting@gmail.com",
"gender" : "male",
"car": {
"model": "i30",
"year": "2013"
},
"phones": [
{
"ddi": "55",
"ddd": "85",
"number": "1111-1111"
},
{
"ddi": "55",
"ddd": "11",
"number": "2222-222"
}
]
}
In Objective-C, we will use the following classes:
typedef enum {
GenderMale,
GenderFemale
} Gender;
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, assign) Gender gender;
@property (nonatomic, strong) Car *car;
@property (nonatomic, strong) NSArray *phones;
@end
@interface Car : NSObject
@property (nonatomic, copy) NSString *model;
@property (nonatomic, copy) NSString *year;
@end
@interface Phone : NSObject
@property (nonatomic, copy) NSString *DDI;
@property (nonatomic, copy) NSString *DDD;
@property (nonatomic, copy) NSString *number;
@end
Implementation
For each class, you need to create a mapping (EKObjectMapping), for example:
EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Person class]];
After that it is necessary to add key-value relations for communication <key in JSON> -> <object property name>. Consider the most common situations when mapping and handling these situations with EasyMapping.
1. The keys in JSON are the same as the property names of the object
Example, Person class:
[mapping mapFieldsFromArray:@[@"name",@"email"]];
2. Keys in JSON are different from object property names.
Example, Phone class:
[mapping mapFieldsFromDictionary:@{@"ddi":@"DDI", @"ddd":@"DDD"}];
3. The value requires additional processing.
Example, class Person
NSDictionary *genders = @{ @"male": @(GenderMale), @"female": @(GenderFemale) };
[mapping mapKey:@"gender" toField:@"gender" withValueBlock:^(NSString *key, id value) {
return genders[value];
}];
4.
An object contains an object of another class. Example, class Person
[mapping hasOneMapping:[Car objectMapping] forKey:@"car"];
5. An object contains an array of objects of another class
Example. Person class
[mapping hasManyMapping:[Phone objectMapping] forKey:@"phones"];
Result
Fully mappings for our classes:
// Car mapping
+ (EKObjectMapping *)objectMapping
{
EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Car class]];
[mapping mapFieldsFromArray:@[@"model", @"year"]];
return mapping;
}
// Phone mapping
+ (EKObjectMapping *)objectMapping
{
EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Phone class]];
[mapping mapFieldsFromArray:@[@"number"]];
[mapping mapFieldsFromDictionary:@{
@"ddi" : @"DDI",
@"ddd" : @"DDD"
}];
return mapping;
}
// Person mapping
+ (EKObjectMapping *)objectMapping
{
EKObjectMapping * mapping = [[EKObjectMapping alloc] initWithObjectClass:[Person class]];
NSDictionary *genders = @{ @"male": @(GenderMale), @"female": @(GenderFemale) };
[mapping mapFieldsFromArray:@[@"name", @"email"]];
[mapping mapKey:@"gender" toField:@"gender" withValueBlock:^(NSString *key, id value) {
return genders[value];
}];
[mapping hasOneMapping:[Car objectMapping] forKey:@"car"];
[mapping hasManyMapping:[Phone objectMapping] forKey:@"phones"];
return mapping;
}
Now that the mapping is ready, the creation of the Person object is as follows:
NSDictionary * personProperties = ...; // JSON обьекта Person, приведенный выше, распарсенный в словарь.
Person * person = [[Person alloc] init];
[EKMapper fillObject:person fromExternalRepresentation:personProperties withMapping:mapping];
Thus, when creating an object, no if-else constructs are used; there are no checks for NSNull. In addition, existing objects can be easily updated with new data if such a need arises.
Extra EasyMapping Buns
1. Support for CoreData
2. Native NSNull verification - NSNull is automatically replaced with nil.
3. The ability to serialize objects in NSDictionary / NSArray
4. Support for installation through CocoaPods
5. Fully covered in tests.
Good practices in implementation
Filling an object with EKMapper is best done in the base class, say BaseModel, as follows:
- (id)initWithProperties:(NSDictionary *)properties
{
if (self = [super init])
{
[EKMapper fillObject:self fromExternalRepresentation:properties
withMapping:[[self class] objectMapping];
}
return self;
}
+ (EKObjectMapping *)objectMapping
{
//Do nothing. Implement in subclass if you want to initialize object
//via object mapping
return nil;
}
Thus, the creation of the object will look like this:
Person * person = [[Person alloc] initWithProperties:personProperties];
Conclusion
Each quality open-source product is a springboard that allows us as programmers to jump higher and higher. The EasyMapping project is only one month old, judging by the first commit, however, in my opinion, it can already compete with much older and more advanced solutions, at least in its simplicity. I would like to wish good luck to the author of this interesting framework, and to everyone who has read to the end - thanks for your time and good luck traveling JSON!
References
1. EasyMapping on github
2. CocoaPods
Alternative solutions
1. RestKit
2. Mantle