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的首字母大写)

- (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,此时第一种方案则比较方便。

当然,现在有更好的解决方案就是github家的力作Mantle,不需要了解KVC便能很容易的将JSON映射为model,有兴趣的可以阅读一下源码,这里就不多言。