用 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

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

时间: 2024-11-04 11:18:37

用 KVC 自动把 JSON 转 Model的相关文章

iOS端JSON转Model链式编程框架SuperKVC使用方法与原理

背景 在client编程中.字典转模型是一个极为常见的问题,苹果提供了KVC来实现NSDictionary到Model的注入,可是KVC仅仅能进行单层浅注入.且无法处理类型转换.key与属性名不正确应.深度注入等问题,笔者从Masonry得到启示,开发了一个通过链式配置注入器实现深度注入.类型转换.key-属性名映射等功能的轻量级注入框架SuperKVC.眼下已经开源到GitHub,点击这里前往.欢迎Star和Fork.欢迎和我一起完好这个框架! 本文将从应用和原理两个角度介绍SuperKVC

Flutter json 2 model with Built Value

Flutter json 2 model with Built Value Flutter中json转换model, 除了手动转之外, 就是利用第三方库做一些代码生成. 流行的库有: json_serializable和built_value 本文介绍built_value的实际使用及问题处理. Flutter中的json转model方法 Flutter中json到model类型的转换可以有多种方式: 利用官方自带的dart convert中的json解码. 该方法只能将json转换为List或

Objective-C KVC 自动转换类型研究

## Objective-C KVC 自动转换类型研究 apple很厚道,kvc的时候帮我们做了一些类型转换,规律贴出来,给大伙参考参考 @interface Entity : NSObject @property (nonatomic, copy) NSString *str; @property (nonatomic, assign) int i1; @property (nonatomic, assign) int i2; @property (nonatomic, assign) int

mybatis genertor自动生成Dao+mapping+model

mybatis genertor自动生成Dao+mapping+model   [1]下载: 可参考:https://github.com/mybatis/generator/releases 解压之后的格式: [2]添加文件 打开lib文件 (1)新建generatorConfig..xml文件,内容见下(仅作参考,部分内容需自己修改): <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE gener

iOS & Mac JSON To Model

NSString * jsonPath=[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Contents.json"]; NSString * jsonStr=[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:jsonPath] encoding:NSUTF8StringEncoding]; NSLog(@"%

iOS项目中Json转Model的坑

Json转Model json转model,是个开发都会遇到过.都已经9102年了,谁还不会用个第三方框架搞.拿起键盘就是干!打开podfile,把大名顶顶的YYModel写上,pod install一下.再用上ESJsonFormat,直接根据json,都能把model生成好. 特殊处理 啥?返回的字段值不是我们所需的在日常开发中,经常会遇到一些接口字段返回的值,并不是我所需要的类型的情况,这个时候,我们都会对这个字段进行处理.举个栗子: 123456 /** 错误代码 */@property

ios kvc json转model的简单现实

在android开发中,可用第三方的转换库如gson等.当然在ios也有一些库如MJExtensiond等.在这里,我仅用官方的简单kvc模式实现一下. 一.先建一个model并且继承NSObject,代码如下: classUser:NSObject{ var name:String? var sex:String? var age:Int=0 override func setValue(_value:Any?, forUndefinedKey key:String) { } } 二.在控制器

让Json和Model相处的融洽点

iOS里服务器返回来的JsonData通过自带的json解析类或者什么JsonKit,SBJson一类的框架都可以方便的完成解析工作,如果不觉得麻烦的话从这里开始通过KVC取值就完全可以搞定了~可对字符串这种东西进行硬编码... 所以转成Model对象还是比较必要的~而json对象到model对象的映射也有很多东西可以做了~诸如JSONModel,Mantle什么的~最值得一提的是传智播客的当家花旦李明杰老师所写的MJExtension~非常出色,尤其在转化效率上~不敢说今天拿出来的这东西比MJ

Objective-C Json转Model(利用Runtime特性)

封装initWithNSDictionary:方法 该方法接收NSDictionary对象, 返回PersonModel对象. #pragma mark - 使用runtime将JSON转成Model (void)json2Model { NSString file = [[NSBundle mainBundle] pathForResource:@"Persons" ofType:@"json"]; NSData data = [NSData dataWithCo