您的位置:首页 > Web前端 > JavaScript

模型转换工具Mantle, MJExtension, JSONModel的使用和异同

2017-02-27 15:50 459 查看
现在大部分的项目都需要将服务器返回的JSON数据转换为Model再使用,手动转换不仅费时费力,还写了一堆重复代码,肯定是不科学的,一般都使用相应的工具来自动转换。目前接触的字典转模型工具有三种,Mantle, MJExtension, JSONModel, 虽然他们做的事情都是一样的,但是使用方法区别还是蛮大的,以及在一些细节上的处理也是不同的。


Mantle的使用

简单的例子就不来了,可以直接到Github上面查看,这里上一个比较典型全面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (NSDictionary *)JSONDict {
if (_JSONDict == nil) {
_JSONDict = @{@"name" : [NSNull null],
@"age" : @20,
@"sex" : @0,
@"login_date" : @"1445136827",
@"phone" : @{
@"name" : @"小明的iPhone",
@"price" : @5000
}
@"books" : @[
@{@"name" : @"西游记"},
@{@"name" : @"三国演义"}
]
};
}
return _JSONDict;
}

对应模型的特点: 1、有NSNull对象, 2、模型里面嵌套模型, 3、模型里面有数组,数组里面有模型.

对应的模型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1819
20
21
22
23
.h
typedef NS_ENUM(NSUInteger, Sex) {
SexMale,
SexFemale
};

@interface BookForMantle : MTLModel <MTLJSONSerializing>
@property (nonatomic, copy, nullable) NSString *name;
@end

@interface PhoneForMantle : MTLModel <MTLJSONSerializing>
@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, assign) double price;
@end

@interface UserForMantle : MTLModel <MTLJSONSerializing>
@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign)  Sex sex;
@property (nonatomic, strong, nullable) NSDate *loginDate;
@property (nonatomic, strong, nullable) PhoneForMantle *phone;
@property (nonatomic, copy, nullable) NSArray<BookForMantle *> *books;
@end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1819
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.m
@implementation PhoneForMantle

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return @{@"name" : @"name",
@"price" : @"price"};
}

@end

@implementation BookForMantle

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return @{@"name" : @"name"};
}

@end

@implementation UserForMantle

// 该map不光是JSON->Model, Model->JSON也会用到
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return @{@"name" : @"name",
@"age" : @"age",
@"sex" : @"sex",
@"loginDate" : @"login_date",
@"phone" : @"phone",
@"books" : @"books"};
}

// 模型里面的模型
+ (NSValueTransformer *)phoneTransformer {
return [MTLJSONAdapter dictionaryTransformerWithModelClass:[PhoneForMantle class]];
}

// 模型里面的数组
+ (NSValueTransformer *)booksTransformer {
return [MTLJSONAdapter arrayTransformerWithModelClass:[BookForMantle class]];
}

// 时间
+ (NSValueTransformer *)loginDateJSONTransformer {
return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *timeIntervalSince1970, BOOL *success, NSError *__autoreleasing *error) {
NSTimeInterval timeInterval = [timeIntervalSince1970 doubleValue];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:timeInterval];
return date;
} reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
NSTimeInterval timeInterval = date.timeIntervalSince1970;
return @(timeInterval).stringValue;
}];
}

@end

对应的解析方法:

1
2
3
4
5
6
7
8
9
10
// 注意: Mantle不会自动转类型,如:String->Int, 一旦类型不匹配,直接crash
// Json->Model
// 该方法会调用key-key map方法。
self.userForMantle = [MTLJSONAdapter modelOfClass:[UserForMantle class] fromJSONDictionary:self.JSONDict error:nil];
// 这种方式只是简单的使用KVC进行赋值。不会调用key-key map方法, 要求属性和JSON字典中的key名称相同,否则就crash
//    self.userForMantle = [UserForMantle modelWithDictionary:self.JSONDict error:&error];

// Model -> JSON
// 一旦有属性为nil, Mantle会转换成NSNull对象放到JSON字典中,这里有一个坑,使用NSUserDefault存储这样的JSON字典时,程序crash,原因是不可以包含NSNull对象。
NSDictionary *jsonDict = [MTLJSONAdapter JSONDictionaryFromModel:self.userForMantle error:nil];


JSOMModel的使用

仍旧使用上面的例子,对应的模型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1819
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// BookForJsonModel
@protocol BookForJsonModel
@end

@interface BookForJsonModel : JSONModel
@property (nonatomic, copy, nullable) NSString *name;
@end

@implementation BookForJSONModel
// 前面是服务器字段,后面是模型属性字段
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:@{
@"name" : @"name"
}];
}
@end

// PhoneForJSONModel
@interface PhoneForJSONModel : JSONModel
@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, assign) double price;
@end

@implementation PhoneForJSONModel
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:@{
@"name" : @"name",
@"price" : @"price"
}];
}
@end

// UserForJSONModel
@interface UserForJSONModel : JSONModel
@property (nonatomic, copy, nullable) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign)  Sex sex;
@property (nonatomic, strong, nullable) NSDate *loginDate;
@property (nonatomic, strong, nullable) PhoneForJSONModel *phone;
// 注意协议
@property (nonatomic, copy, nullable) NSArray<BookForJSONModel> *books;
@end

@implementation UserForJSONModel
+ (JSONKeyMapper *)keyMapper {
return [[JSONKeyMapper alloc] initWithDictionary:@{
@"name" : @"name",
@"age" : @"age",
@"sex" : @"sex",
@"login_date" : @"loginDate",
@"phone" : @"phone",
@"books" : @"books"
}];
}

// 允许所有字段为空
+ (BOOL)propertyIsOptional:(NSString *)propertyName {
return YES;
}
@end

对应的解析方法

1
2
3
4
// JSON->Model
UserForJSONModel *user = [[UserForJSONModel alloc] initWithDictionary:self.JSONDict error:nil];
// Model->JSON
NSDictionary *dict = [user toDictionary];

JSONModel各方面都挺好的,唯一需要注意的地方是它归档的方式, 它不是将对象归档,而是转换成字典再归档。

1
2
3
4
5
6
7
8
9
1011
12
13
14
15
16
-(instancetype)initWithCoder:(NSCoder *)decoder
{
NSString* json = [decoder decodeObjectForKey:@"json"];

JSONModelError *error = nil;
self = [self initWithString:json error:&error];
if (error) {
JMLog(@"%@",[error localizedDescription]);
}
return self;
}

-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.toJSONString forKey:@"json"];
}


MJExtension的使用

这个的使用方式就不介绍了,GitHub上写的非常详细。这里主要说说我在用的项目中时遇到问题及解决方式。情况大致是这样的:
最开始项目中的模型统统继承自BaseModel类,解析方式都是自己挨个手动解析。还自定义了一些譬如时间戳转自定义日期类型的方法。在换到MJExtension时,没法对我们的自定义解析方式进行兼容,全部重写肯定是不现实的,只能做兼容。最后通过阅读MJExtension的源码,找到了一个突破口。在BaseModel里面对MJExtension里面的一个方法使用Method Swizzling进行替换。大致代码如下:

1
2
3
BaseModel.h 里面添加一个接口,子类可以覆盖
/// json->模型,转换完成之后调用, 以便进行自定义配置。
- (void)mc_keyValuesDidFinishConvertingToObjectWithData:(id)data;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1819
20
21
22
23
24
25
26
27
28
BaseModel.m
/* 替换方法: - (instancetype)setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context error:(NSError **)error; */
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

SEL originalSelector = @selector(setKeyValues:context:error:);
SEL swizzledSelector = @selector(mc_setKeyValues:context:error:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (success) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

- (instancetype)mc_setKeyValues:(id)keyValues context:(NSManagedObjectContext *)context error:(NSError **)error {
MCDataModel *model = [self mc_setKeyValues:keyValues context:context error:error];
if ([self respondsToSelector:@selector(mc_keyValuesDidFinishConvertingToObjectWithData:)]) {
[self mc_keyValuesDidFinishConvertingToObjectWithData:keyValues];
}
return model;
}

这样在使用MJExtension将模型解析完成之后再调用mc_keyValuesDidFinishConvertingToObjectWithData: 将原始数据传递过去进行自定义配置。就可以很好的与老工程兼容了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: