程序员人生 网站导航

用 KVC 自动把 JSON 转 Model

栏目:综合技术时间:2015-03-27 08:31:56


图1和图2是1个接口,code 是在服务器修改或升级等缘由致使的;图3是在新用户登录没有数据的情况出现的;是1个接口对应的Model类也是1个;Model类代码以下

@interface SHYProduct : NSObject @property (nonatomic, assign) int code; @property (nonatomic, strong) NSString *msg; @property (nonatomic, strong) NSArray *data; @end @interface SHYProductItem : NSObject @property (nonatomic, strong) NSString *title; @end #import "SHYProduct.h" @implementation SHYProduct - (void)dealloc { _msg = nil; _data = nil; } @end @implementation SHYProductItem - (void)dealloc { _title = nil; } @end

之前我们在转Model是这样写的

NSString *json = @"{"code":"200","msg":"u83b7u53d6u6210u529f","data":[{"title":"title 3"},{"title":"title 4"}]}"; NSData *jsonData = [json dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *body = kNSDictionary([NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil]); SHYProduct *product = [[SHYProduct alloc] init]; product.code = [body intForKey:@"code"]; product.msg = [body stringForKey:@"msg"]; NSArray *rows = [body arrayForKey:@"data"]; NSMutableArray *items = [[NSMutableArray alloc] init]; for (id row in rows) { NSDictionary *dictionary = kNSDictionary(row); SHYProductItem *item = [[SHYProductItem alloc] init]; item.title = [dictionary stringForKey:@"title"]; [items addObject:item]; } product.data = items;

这样写没有甚么错,唯1的是代码大,体力活;

关于 intForKey 之类的方法请看 网络接口协议 JSON 解析 Crash 的哪些事

如果我们不想做这个体力活;有无办法呢;办法是有1个的,用KVC + Runtime;在用这个之前我们要确认1点用KVC code字段有数字和字符串能不能转成int类型;是不是可以测试1下就知道;代码以下:

NSString *json = @"{"code":"200","msg":"u83b7u53d6u6210u529f","data":[{"title":"title 3"},{"title":"title 4"}]}"; NSData *jsonData = [json dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *body = kNSDictionary([NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil]); SHYProduct *product = [[SHYProduct alloc] init]; [product setValue:[body valueForKey:@"code"] forKey:@"code"];//这里会转成数字,KVC自动转换了 [product setValue:[body valueForKey:@"msg"] forKey:@"msg"]; NSArray *rows = [body arrayForKey:@"data"]; NSMutableArray *items = [[NSMutableArray alloc] init]; for (id row in rows) { NSDictionary *dictionary = kNSDictionary(row); SHYProductItem *item = [[SHYProductItem alloc] init]; [item setValue:[dictionary valueForKey:@"title"] forKey:@"title"]; [items addObject:item]; } product.data = items;

运行1下code值过去了;JSON的code是数字和字符串最后都能变成int类型;这下好办了写1个Model基类就能够替换体力活了;代码以下:(开源代码https://github.com/elado/jastor)

#import <Foundation/Foundation.h> /*! @class SHYJastor @abstract 把 NSDictionary 转成 model 用的 */ @interface SHYJastor : NSObject<NSCoding> /*! @property objectId @abstract 对象的id */ @property (nonatomic, copy) NSString *objectId; /*! @method objectWithDictionary: @abstract 指定 NSDictionary 对象转成 model 对象 @param dictionary NSDictionary的对象 @result 返回 model 对象 */ + (id)objectWithDictionary:(NSDictionary *)dictionary; /*! @method initWithDictionary: @abstract 指定 NSDictionary 对象转成 model 对象 @param dictionary NSDictionary的对象 @result 返回 model 对象 */ - (id)initWithDictionary:(NSDictionary *)dictionary; /*! @method dictionaryValue @abstract 把对象转成 NSDictionary 对象 @result 返回 NSDictionary 对象 */ - (NSMutableDictionary *)dictionaryValue; /*! @method mapping @abstract model 属性 与 NSDictionary 不1至时的映照 */ - (NSDictionary *)mapping; @end #import "SHYJastor.h" #import "SHYJastorRuntimeHelper.h" #import "NSArray+SHYUtil.h" #import "NSDictionary+SHYUtil.h" static NSString *idPropertyName = @"id"; static NSString *idPropertyNameOnObject = @"objectId"; @implementation SHYJastor Class dictionaryClass; Class arrayClass; + (id)objectWithDictionary:(NSDictionary *)dictionary { id item = [[self alloc] initWithDictionary:dictionary]; return item; } - (id)initWithDictionary:(NSDictionary *)dictionary { if (!dictionaryClass) dictionaryClass = [NSDictionary class]; if (!arrayClass) arrayClass = [NSArray class]; self = [super init]; if (self) { NSDictionary *maps = [self mapping]; NSArray *propertys = [SHYJastorRuntimeHelper propertyNames:[self class]]; for (NSDictionary *property in propertys) { NSString *propertyName = [property stringForKey:@"name"]; id key = [maps valueForKey:propertyName]; id value = [dictionary valueForKey:key]; if (value == [NSNull null] || value == nil) { continue; } if ([SHYJastorRuntimeHelper isPropertyReadOnly:[property stringForKey:@"attributes"]]) { continue; } if ([value isKindOfClass:dictionaryClass]) { Class aClass = NSClassFromString([property stringForKey:@"type"]); if (![aClass isSubclassOfClass:[NSDictionary class]]) { continue; } value = [[aClass alloc] initWithDictionary:value]; } else if ([value isKindOfClass:arrayClass]) { NSArray *items = (NSArray *)value; NSMutableArray *objects = [NSMutableArray arrayWithCapacity:[items count]]; for (id item in items) { if ([[item class] isSubclassOfClass:dictionaryClass]) { SEL selector = NSSelectorFromString([NSString stringWithFormat:@"%@Class", propertyName]); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" Class aClass = ([[self class] respondsToSelector:selector]) ? [[self class] performSelector:selector] : nil; #pragma clang diagnostic pop if ([aClass isSubclassOfClass:[NSDictionary class]]) { [objects addObject:item]; } else if ([aClass isSubclassOfClass:[SHYJastor class]]) { SHYJastor *childDTO = [[aClass alloc] initWithDictionary:item]; [objects addObject:childDTO]; } } else { [objects addObject:item]; } } value = objects; } [self setValue:value forKey:propertyName]; } id objectId; if ((objectId = [dictionary objectForKey:idPropertyName]) && objectId != [NSNull null]) { if (![objectId isKindOfClass:[NSString class]]) { objectId = [NSString stringWithFormat:@"%@", objectId]; } [self setValue:objectId forKey:idPropertyNameOnObject]; } } return self; } - (void)dealloc { _objectId = nil; } - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:_objectId forKey:idPropertyNameOnObject]; NSArray *propertys = [SHYJastorRuntimeHelper propertyNames:[self class]]; for (NSDictionary *property in propertys) { NSString *propertyName = [property stringForKey:@"name"]; [encoder encodeObject:[self valueForKey:propertyName] forKey:propertyName]; } } - (id)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { [self setValue:[decoder decodeObjectForKey:idPropertyNameOnObject] forKey:idPropertyNameOnObject]; NSArray *propertys = [SHYJastorRuntimeHelper propertyNames:[self class]]; for (NSDictionary *property in propertys) { NSString *propertyName = [property stringForKey:@"name"]; if ([SHYJastorRuntimeHelper isPropertyReadOnly:[property stringForKey:@"attributes"]]) { continue; } id value = [decoder decodeObjectForKey:propertyName]; if (value != [NSNull null] && value != nil) { [self setValue:value forKey:propertyName]; } } } return self; } - (NSMutableDictionary *)dictionaryValue { NSMutableDictionary *infos = [NSMutableDictionary dictionary]; if (_objectId) { [infos setObject:_objectId forKey:idPropertyName]; } NSDictionary *maps = [self mapping]; NSArray *propertys = [SHYJastorRuntimeHelper propertyNames:[self class]]; for (NSDictionary *property in propertys) { NSString *propertyName = [property stringForKey:@"name"]; id value = [self valueForKey:propertyName]; if (value && [value isKindOfClass:[SHYJastor class]]) { [infos setObject:[value dictionary] forKey:[maps valueForKey:propertyName]]; } else if (value && [value isKindOfClass:[NSArray class]] && ((NSArray *)value).count > 0) { id internalValue = [value objectForKeyCheck:0]; if (internalValue && [internalValue isKindOfClass:[SHYJastor class]]) { NSMutableArray *internalItems = [NSMutableArray array]; for (id item in value) { [internalItems addObject:[item dictionary]]; } [infos setObject:internalItems forKey:[maps valueForKey:propertyName]]; } else { [infos setObject:value forKey:[maps valueForKey:propertyName]]; } } else if (value != nil) { [infos setObject:value forKey:[maps valueForKey:propertyName]]; } } return infos; } - (NSDictionary *)mapping { NSArray *properties = [SHYJastorRuntimeHelper propertyNames:[self class]]; NSMutableDictionary *maps = [[NSMutableDictionary alloc] initWithCapacity:properties.count]; for (NSDictionary *property in properties) { NSString *propertyName = [property stringForKey:@"name"]; [maps setObject:propertyName forKey:propertyName]; } return maps; } - (NSString *)description { NSMutableDictionary *dictionary = [self dictionaryValue]; return [NSString stringWithFormat:@"#<%@: id = %@ %@>", [self class], _objectId, [dictionary description]]; } - (BOOL)isEqual:(id)object { if (object == nil || ![object isKindOfClass:[SHYJastor class]]) { return NO; } SHYJastor *model = (SHYJastor *)object; return [_objectId isEqualToString:model.objectId]; } @end @interface SHYJastorRuntimeHelper : NSObject + (BOOL)isPropertyReadOnly:(NSString *)attributes; + (NSArray *)propertyNames:(__unsafe_unretained Class)aClass; @end #import <objc/runtime.h> #import "SHYJastor.h" #import "SHYJastorRuntimeHelper.h" #import "NSArray+SHYUtil.h" #import "NSDictionary+SHYUtil.h" #include <string.h> static NSMutableDictionary *propertyListByClass; static const char *property_getTypeName(const char *attributes) { char buffer[strlen(attributes) + 1]; strncpy(buffer, attributes, sizeof(buffer)); char *state = buffer, *attribute; while ((attribute = strsep(&state, ",")) != NULL) { if (attribute[0] == 'T') { size_t len = strlen(attribute); attribute[len - 1] = ''; static char result[256]; strncpy(result, attribute + 3, len - 2); return result; } } return "@"; } @implementation SHYJastorRuntimeHelper + (void)initialize { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } + (void)didReceiveMemoryWarning { [propertyListByClass removeAllObjects]; } + (BOOL)isPropertyReadOnly:(NSString *)attributes { NSArray *items = [attributes componentsSeparatedByString:@","]; NSString *attribute = [items stringAtIndex:1]; return [attribute rangeOfString:@"R"].length > 0; } + (NSArray *)propertyNames:(__unsafe_unretained Class)aClass { if (aClass == [SHYJastor class]) { return [NSArray array]; } if (!propertyListByClass) { propertyListByClass = [[NSMutableDictionary alloc] init]; } NSString *className = NSStringFromClass(aClass); NSArray *names = [propertyListByClass arrayForKey:className]; if (names) { return names; } NSMutableArray *items = [NSMutableArray array]; unsigned int itemCount = 0; objc_property_t *propertys = class_copyPropertyList(aClass, &itemCount); for (unsigned int i = 0; i < itemCount; ++i) { objc_property_t property = propertys[i]; const char *name = property_getName(property); const char *attributes = property_getAttributes(property); const char *typeName = property_getTypeName(attributes); NSMutableDictionary *item = [NSMutableDictionary dictionary]; [item setObject:[NSString stringWithUTF8String:name] forKey:@"name"]; [item setObject:[NSString stringWithUTF8String:attributes] forKey:@"attributes"]; [item setObject:[NSString stringWithUTF8String:typeName] forKey:@"type"]; [items addObject:item]; } free(propertys); [propertyListByClass setObject:items forKey:className]; NSArray *array = [SHYJastorRuntimeHelper propertyNames:class_getSuperclass(aClass)]; [items addObjectsFromArray:array]; return items; } @end

用这个基类Model类也要修改1下;代码以下:

@interface SHYProduct : SHYJastor @property (nonatomic, assign) int code; @property (nonatomic, strong) NSString *msg; @property (nonatomic, strong) NSArray *data; @end @interface SHYProductItem : SHYJastor @property (nonatomic, strong) NSString *title; @end @implementation SHYProduct + (Class)dataClass//data; 里的对象类型 { return [SHYProductItem class]; } - (void)dealloc { _msg = nil; _data = nil; } @end @implementation SHYProductItem - (void)dealloc { _title = nil; } @end

外部用就很方便;代码以下:

NSString *json = @"{"code":"200","msg":"u83b7u53d6u6210u529f","data":[{"title":"title 3"},{"title":"title 4"}]}"; NSData *jsonData = [json dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *body = kNSDictionary([NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil]); SHYProduct *product = [[SHYProduct alloc] initWithDictionary:body];

如果我们Model写的更好1点,我们可以把 data 属性改成 items 这样就更好了,代码以下:

@interface SHYProduct : SHYJastor @property (nonatomic, assign) int code; @property (nonatomic, strong) NSString *msg; @property (nonatomic, strong) NSArray *items; @end @interface SHYProductItem : SHYJastor @property (nonatomic, strong) NSString *title; @end @implementation SHYProduct + (Class)itemsClass//items; 里的对象类型 { return [SHYProductItem class]; } - (void)dealloc { _msg = nil; _items = nil; } - (NSDictionary *)mapping { NSMutableDictionary *maps = [NSMutableDictionary dictionaryWithDictionary:[super mapping]]; [maps setObject:@"data" forKey:@"items"];//字段与属性不1致,写1个映照就能够 return maps; } @end @implementation SHYProductItem - (void)dealloc { _title = nil; } @end

看到了吧,方便吧,体力活再见

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐