APP端经常需要与服务器交互,json格式的数据是经常被使用的,解析json是经常要做的一件事。
先看一个示例,一般情况下,我们是这么解析的:
// main.m // Demo #import <Foundation/Foundation.h> #import "Person.h" #import "Box.h" int main(int argc, const char * argv[]) { /** * { * "name":"Mr.Li", * "number":"007", * "box": { * "pen":"Hero", * "phone":"iPhone 10" * "pad":"iPad Air" * } * } */ NSString *jsonString = @"{"name":"Mr.Li", "number":"007", "box":{ "pen":"Hero", "phone":"iPhone 10", "pad":"iPad Air" } }"; NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error]; Person *person = [[Person alloc] init]; [person setName:jsonDic[@"name"]]; [person setNumber:jsonDic[@"number"]]; NSDictionary *boxDic = jsonDic[@"box"]; Box *box = [[Box alloc] init]; [box setPen:boxDic[@"pen"]]; [box setPhone:boxDic[@"phone"]]; [box setPad:boxDic[@"pad"]]; person.myBox = box; NSLog(@"%@",person); retun 0; ?}
Person 类(略去Person.m,仅仅重写了description方法):
// Person.h // Demo #import <Foundation/Foundation.h> #import "Box.h" @interface Person : NSObject @property (copy,nonatomic) NSString *name; @property (copy,nonatomic) NSString *number; @property (strong,nonatomic) Box *myBox; @end
Box 类 (略去Box.m,仅仅重写了description方法):
// Box.h // Demo #import <Foundation/Foundation.h> @interface Box : NSObject @property (copy,nonatomic) NSString *pen; @property (copy,nonatomic) NSString *phone; @property (copy,nonatomic) NSString *pad; @end
关键是这样的,当APP端有大量数据需要与服务器进行交互的时候,这么一条一条解析绝笔是相当累的(纯体力活)。
作为一个有理想的码农,就得想一个比较好的办法。
———— 秘诀:KVC ————
什么是KVC?即Key-Value Coding。简单来说,就是通过一个字符串key来设置该key对应属性的值。
举个简单的例子:
[person setValue:@"Mr.Zhang" forKey:@"name"]; NSLog(@"name:%@",person.name); /** * 运行结果: * name:Mr.Zhang */
KVC的实现原理就是反射,通过@"name"这个字符串映射出对应的属性然后进行修改。
在Persion.m中添加parsingDataFromDictionary:方法
- (void)parsingDataFromDictionary:(NSDictionary *)parameters { for (NSString *key in parameters) { id value = parameters[key]; if (value != [NSNull null] && value != nil){ [self setValue:parameters[key] forKey:key]; } else { [self setValue:[NSNull null] forKey:key]; } } }
这样运行起来会有问题,就是当key为box时候,会导致程序出错
有两种方案解决这个问题
解决之前,先在Box.m 中实现parsingDataFromDictionary:方法,(其实方法体完全一样的,这里暂且那么写,后面会讲到可以进行封装起来),备用
第一种:
修改parsingDataFromDictionary:方法(黑体字为新增代码),
for (NSString *key in parameters) { id value = parameters[key]; NSError *error = nil; if([self validateValue:&value forKey:key error:&error]){ if (value != [NSNull null] && value != nil){ [self setValue:parameters[key] forKey:key]; } else { [self setValue:[NSNull null] forKey:key]; } } }
在Person.m中增加validateName:error:方法(Name为<key>,key的首字母大写)
- (BOOL)validateBox:(id *)ioValue error:(NSError *__autoreleasing *)error { if ([*ioValue isKindOfClass:[NSDictionary class]]) { Box *box = [[Box alloc]initWithDictionary:*ioValue]; *ioValue = box; } return YES; }
<!!! 注意:是validateName:error:方法,而不是重写validateValue:forKey:error: !!!>
第二种:
在Person.m中,增加setValue:forUndefinedKey:方法,
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"未定义的Key:%@, value:%@",key,value); if ([key isEqualToString:@"box"] && [value isKindOfClass:[NSDictionary class]]) { Box *box = [[Box alloc] initWithDictionary:value]; self.myBox = box; } }
总得来说,第一种方案是在赋值之前进行检测并订正,第二种方案则是赋值失败之后,再进行订正。
以上两种解决方案都可以,看似第一种麻烦一些,但是就比如当Person.Box的实例也叫box的时候,第二种方法是解决不了的,会导致直接将box字典,强制赋值给person.box,此时第一种方案则比较方便。