用 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

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

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。