用 KVC 自动把 JSON 转 Model
来源:程序员人生 发布时间:2015-03-27 08:31:56 阅读次数:4034次
图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] = '