昨晚,学习了一下 Mantle,参照的是这篇 blog ,首先感谢原文作者,基本熟悉了 Mantle 的使用方法,但那篇文章比较久远了,Mantle 现在对类型转换加入了 ErrorHandling 机制,如果你的 model 声明类型和解析 Json 得到的不一致,就会绑定失败,这点要特别注意。
原文还有一个坑要注意的是大家注意解析到的 json , "Weather"
对应的 value
是一个 array
因此在 + (NSDictionary *)JSONKeyPathsByPropertyKey
方法中这样写是绑定不成功的,会导致整个 Model 为 nil
+ (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ ... @"conditionDescription": @"weather.description", ... }; }
和K神 的讨论是 这种情况比较麻烦一些,需要创建一个新的 model,然后将他反序列化出来 。但我们还是想用 weather.description,毕竟这种最方便么。于是我打断点跑了一下 Mantle 的源码,发现主要是 "NSDictionary+MTLJSONKeyPath.h/NSDictionary+MTLJSONKeyPath.m" 这个类在控制着解析过程,我们来具体看一下:
#import "NSDictionary+MTLJSONKeyPath.h" #import "MTLJSONAdapter.h" @implementation NSDictionary (MTLJSONKeyPath) - (id)mtl_valueForJSONKeyPath:(NSString *)JSONKeyPath success:(BOOL *)success error:(NSError **)error { //1. 传进来的 JSONKeyPath 其实就是 "weather.description",然后把他们每一部分都放进数组中 NSArray *components = [JSONKeyPath componentsSeparatedByString:@"."]; //2. 遍历 componets 数组 id result = self; for (NSString *component in components) { // Check the result before resolving the key path component to not // affect the last value of the path. if (result == nil || result == NSNull.null) break; //3. 检查 result,不是字典对象就返回错误,并最终返回 nil if (![result isKindOfClass:NSDictionary.class]) { if (error != NULL) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid JSON dictionary", @""), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"JSON key path %1$@ could not resolved because an incompatible JSON dictionary was supplied: /"%2$@/"", @""), JSONKeyPath, self] }; *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorInvalidJSONDictionary userInfo:userInfo]; } if (success != NULL) *success = NO; return nil; } // 4. 取出相应 key 所对应的值 result = result[component]; } // 5. 成功并返回 if (success != NULL) *success = YES; return result; } @end
上面就是解析 "aaa.bbb" 这种语法的写法,我们观察一下 注释 3 就能发现该写法只支持 NSDictionary 对象,也就是说,只有 "aaa.bbb.ccc.ddd.eee"
中的 "aaa", "bbb", "ccc", "ddd", "eee" 都为 NSDictionary 对象时,才能解析成功,如果其中任意一个为数组对象就会解析失败,例如我们上面的 weather.description
,知道原因了,就好办多了,让我们来修改一下。
首先增加一个 NSArray 的 Category NSArray+NTLJSONKeyPath
,内容和 NSDictionary+MTLJSONKeyPath
差不多,来看下实现:
#import "NSArray+NTLJSONKeyPath.h" #import "MTLJSONAdapter.h" @implementation NSArray (NTLJSONKeyPath) - (id)mtl_valueForJSONKeyPath:(NSString *)JSONKeyPath success:(BOOL *)success error:(NSError **)error { NSUInteger count = self.count; id result; for (int i = 0; i < count; i++) { // Check the result before resolving the key path component to not // affect the last value of the path. result = self[i]; if (result == nil || result == NSNull.null) break; if (![result isKindOfClass:NSDictionary.class] && ![result isKindOfClass:[NSArray class]]) { if (error != NULL) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid JSON dictionary", @""), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"JSON key path %1$@ could not resolved because an incompatible JSON dictionary was supplied: /"%2$@/"", @""), JSONKeyPath, self] }; *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorInvalidJSONDictionary userInfo:userInfo]; } if (success != NULL) *success = NO; return nil; } if ([result isKindOfClass:NSDictionary.class]) { result = result[JSONKeyPath]; if (result) break; } else if ([result isKindOfClass:[NSArray class]]) { result = [(NSArray *)result mtl_valueForJSONKeyPath:JSONKeyPath success:success error:error]; } } if (success != NULL) *success = YES; return result; }
代码也非常简单,如果遇到数组了,就遍历,如果是字典就根据当前 key,找出对应的 value,如果 value 还是数组,就继续递归执行。接着把 NSDictionary+MTLJSONKeyPath
小修改一下:
#import "NSDictionary+MTLJSONKeyPath.h" #import "NSArray+NTLJSONKeyPath.h" #import "MTLJSONAdapter.h" @implementation NSDictionary (MTLJSONKeyPath) - (id)mtl_valueForJSONKeyPath:(NSString *)JSONKeyPath success:(BOOL *)success error:(NSError **)error { NSArray *components = [JSONKeyPath componentsSeparatedByString:@"."]; id result = self; for (NSString *component in components) { // Check the result before resolving the key path component to not // affect the last value of the path. if (result == nil || result == NSNull.null) break; // 1. 这里增加了对 result 是否为数组的判断,如果是数组,就不要返回 nil 了 if (![result isKindOfClass:NSDictionary.class] && ![result isKindOfClass:[NSArray class]]) { if (error != NULL) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: NSLocalizedString(@"Invalid JSON dictionary", @""), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"JSON key path %1$@ could not resolved because an incompatible JSON dictionary was supplied: /"%2$@/"", @""), JSONKeyPath, self] }; *error = [NSError errorWithDomain:MTLJSONAdapterErrorDomain code:MTLJSONAdapterErrorInvalidJSONDictionary userInfo:userInfo]; } if (success != NULL) *success = NO; return nil; } //2. 判断 result 的类型,执行相应的方法 if ([result isKindOfClass:NSDictionary.class]) { result = result[component]; } else if ([result isKindOfClass:[NSArray class]]) { result = [(NSArray *)result mtl_valueForJSONKeyPath:component success:success error:error]; } } if (success != NULL) *success = YES; return result; }
只用修改上面两处,Binggo,一个可以处理 weather.description
的 Mantle 就改好啦,什么? weather.description
太简单,那我们试一下面的 'weather.pp.aa.dd'
解析结果:
是不是还不错呢,本篇的 demo 和 json 我都上传到 github 上了,可以自行试验,也许会有 bug,不过有什么问题可以给我留言