SuperKVC is a light-weight injection framework to convert JSON to Model. SuperKVC has its own config DSL which provides a chainable way of describing your injection config concise and readable. SuperKVC supports iOS and macOS.
Samples are at the SuperKVCDemo project in the SuperKVC workspace. There are three sample to show shallow and deep injection configurations.
What's wrong with KVC?
Apple provide KVC for us to inject dictionary to model, but it cannot solve problems below.
-
KVC simplely mapping the dictionary key to model key, but many times their name are not the same. For example,
idis a keyword in Objective-C while not a keyword in backend, so many network interface responseidas a key, in Objective-C model, we may useuserId, soidcannot mapping touserId, if we use KVC in this time, the app will crash because there are not aidproperty in the model. -
KVC cannot handle data format convert, for example, our
UserModelhas aNSDatepropertybirthday, while the network interface response adate string, we need to useNSDateFormatterto convert, but KVC cannot config a converter, so it inject aNSStringto aNSDateproperty. -
KVC cannot handle deep injection, for example, our
UserModelhas aCardModelpropertycard, while the network interface response aNSDictionary, we need to inject the dictionary to card property, but KVC only inject one level.
Prepare to meet your Injector!
Think about we have the response and the model structure below.
{
"id": 100075,
"name": "Greedy",
"birthday": "1993-03-06",
"isVip": true,
"partners": [100236, 100244, 100083]
}@interface UserModel : NSObject
@property (nonatomic, assign) int64_t userId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;
@property (nonatomic, assign) BOOL isVip;
@property (nonatomic, strong) NSArray *partners;
@endNow we need to convert json to UserModel instance, please note that the response key id is called userId in the model, and the birthday property type is not a NSString.
By using SuperKVC, you even not need to create the model, tell the injector your config, and it returns what you want.
// responseObject is a JSONObject(NSDictionary).
UserModel *userModel = [responseObject sk_injectWithInjector:^(SuperKVCInjector *injector) {
injector.bind([UserModel class]);
injector.mapping(@"id").to(@"userId");
injector.format(@"birthday").with.converter(^NSDate* (NSString *birthdayString) {
NSDateFormatter *fmt = [NSDateFormatter new];
fmt.dateFormat = @"yyyy-MM-dd";
return [fmt dateFromString:birthdayString];
});
}];The config descriptions are below.
| Name | Function | Usage |
|---|---|---|
| bind | tell the injector what model should be created and injected. | injector.bind(#Class#); |
| mapping | tell the injector to mapping the response key to another property name. | injector.mapping(#responseKey#).to(#propertyName#); |
| format | format a property with a block filter. | injector.format(#propertyName#).with.converter(^id (id oldVar) { /* your format code */ return #newVar#; }); |
| ignore | ignore some property which should not be injected. note that ignores can be joined by and in one line. |
injector.ignore(#propName1#).and(#propName2#) ... |
| synthesize | to tell the injector mapping a property to a specific ivar. such as when @synthesize userId = userId_, the injector cannot get ivar by default | injector.synthesize(#propertyName#).to(#ivarName#); |
Array Injection
Think about we have another json below.
[{
"id": 100075,
"name": "Greedy",
"birthday": "1993-03-06",
"isVip": true,
"partners": [100236, 100244, 100083]
},
{
"id": 100724,
"name": "Charlie",
"birthday": "1996-08-12",
"isVip": false,
"partners": [100710, 100715]
},{},]We needn't to modify any codes to get a UserModel array.
// responseObject is a JSONObject(NSArray).
NSArray<UserModel *> *userModels = [responseObject sk_injectWithInjector:^(SuperKVCInjector *injector) {
injector.bind([UserModel class]);
injector.mapping(@"id").to(@"userId");
injector.format(@"birthday").with.converter(^NSDate* (NSString *birthdayString) {
NSDateFormatter *fmt = [NSDateFormatter new];
fmt.dateFormat = @"yyyy-MM-dd";
return [fmt dateFromString:birthdayString];
});
}];Deep Injection
Think about we have a two level json.
[{
"id": 100075,
"name": "Greedy",
"birthday": "1993-03-06",
"isVip": false,
"cards": [
{
"id": 400820666,
"name": "King Card of Unity",
"expire": "2026-03-27"
},
{
"id": 622800333,
"name": "Silver Card of Glory",
"expire": "2029-02-21"
},
{
"id": 623400765,
"name": "King Card of Floyt",
"expire": "2024-08-15"
}
]
},{},]And our class structures are below.
@interface UserModel : NSObject
@property (nonatomic, assign) int64_t userId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *birthday;
@property (nonatomic, assign) BOOL isVip;
@property (nonatomic, strong) NSArray<CardModel *> *cards;
@end
@interface CardModel : NSObject
@property (nonatomic, assign) int64_t cardId;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSDate *expireDate;
@endTo make a deep injection, we need to use the converter to nested call injector on the cards property. To be efficient, we create the NSDateFormatter out of the block and resue it.
// responseObject is a JSONObject(NSArray).
NSDateFormatter *fmt = [NSDateFormatter new];
fmt.dateFormat = @"yyyy-MM-dd";
NSArray *userArray = [responseObject sk_injectWithInjector:^(SuperKVCInjector *injector) {
injector.bind([UserModel class]);
injector.mapping(@"id").to(@"userId");
injector.format(@"birthday").with.converter(^NSDate* (NSString *birthdayString) {
return [fmt dateFromString:birthdayString];
});
injector.format(@"cards").with.converter(^CardModel* (NSDictionary *cardDictArray) {
return [cardDictArray sk_dequeInjectorForClass:[CardModel class] emptyHandler:^(SuperKVCInjector *injector) {
injector.bind([CardModel class]);
injector.mapping(@"id").to(@"cardId");
injector.mapping(@"expire").to(@"expireDate");
injector.format(@"expireDate").with.converter(^NSDate* (NSString *birthdayString) {
return [fmt dateFromString:birthdayString];
});
}];
});
}];Please note that we use sk_dequeInjectorForClass:: method in the nested call, it can handle injector reuse for class to improve execution efficiency.
Installation
- Clone the repo, and drag the
SuperKVCfolder to your project. #import "SuperKVC.h"
Cache Configuration
The SuperKVC framework cached reflection result and injector configuration in memory using a 10 limit LRU(NSCache), you can change the limit or clear cache by the property cacheLimit and the method claerCache in SKVManager singleton.
TODO
- Disk cache.
- More tests and examples.