用 KVC 自动把 JSON 转 Model
图1和图2是一个接口,code 是在服务器修改或升级等原因导致的;图3是在新用户登录没有数据的情况出现的;是一个接口对应的Model类也是一个;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\":\"\u83b7\u53d6\u6210\u529f\",\"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;
这样写没有什么错,唯一的是代码大,体力活;
关于 intForKey 之类的方法请看 网络接口协议 JSON 解析 Crash 的哪些事如果我们不想做这个体力活;有没有办法呢;办法是有一个的,用KVC + Runtime;在用这个之前我们要确认一点用KVC code字段有数字和字符串能不能转成int类型;是否可以测试一下就知道;代码如下:
NSString *json = @"{\"code\":\"200\",\"msg\":\"\u83b7\u53d6\u6210\u529f\",\"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;
运行一下code值过去了;JSON的code是数字和字符串最后都能变成int类型;这下好办了写一个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 不一至时的映射 */ - (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] = '\0'; 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类也要修改一下;代码如下:
@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\":\"\u83b7\u53d6\u6210\u529f\",\"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写的更好一点,我们可以把 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"];//字段与属性不一致,写一个映射就可以 return maps; } @end @implementation SHYProductItem - (void)dealloc { _title = nil; } @end
看到了吧,方便吧,体力活再见
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。