让Json和Model相处的融洽点

iOS里服务器返回来的JsonData通过自带的json解析类或者什么JsonKit,SBJson一类的框架都可以方便的完成解析工作,如果不觉得麻烦的话从这里开始通过KVC取值就完全可以搞定了~可对字符串这种东西进行硬编码...

所以转成Model对象还是比较必要的~而json对象到model对象的映射也有很多东西可以做了~诸如JSONModel,Mantle什么的~最值得一提的是传智播客的当家花旦李明杰老师所写的MJExtension~非常出色,尤其在转化效率上~不敢说今天拿出来的这东西比MJExtension好,只能说更符合笔者自己简单粗暴有效的开发和使用习惯~

经过测试,用我的Macbook对后文给出的1000个相同的测试词典所组成的数组进行映射操作,MJExtension耗时仅0.07秒,而我写的这套工具则花了0.25秒左右...耗时在3~4倍的区间~由于是通过对于模型名称和类的名称进行判断和字符串处理整合来进行映射,更高的耗时也是自然的~通过GCD的并发操作进行处理也并没有太明显的效率提升~

考虑后得到的结论是对每一个模型进行转化的时候都需要对齐内部结构进行完整扫描,对于单个模型的转化这是自然,但是对于数组的整体映射,由于数组保存的模型是相同的,那么长度1000的数组有999次扫描都是没必要的~1:1的线性耗时增加造成了整体的效率降低~作为第一版就先这样吧,毕竟即使没有通过缓存类结构来提升效率,1000个模型0.25秒的映射速度目前来看也还是可以接受的~

接下来说说这究竟是个什么东西

先看一组用来做测试的假数据

-(NSDictionary *)fakeData{
    return
@{
@"name":@"jack",@"id":@"123",@"code":@"4567890-",
@"other":@{@"attr1":@"value1",@"attr2":@"150",@"attr3":@"0.8"},
@"test":@"3",
@"d":@"4.5",
@"l":@3456789,
@"f":@"123.123",
@"b":@"1",
@"date1":@"13567890456",
@"date2":@"2015-4-3 11:18:24",
@"arr":@[
    @{@"attr1":@"value1",@"attr2":@"150",@"attr3":@"0.8"},
    @{@"attr1":@"value1",@"attr2":@"150",@"attr3":@"0.8"}],
@"path":@{
    @"third":@{@"name":@"rose",@"age":@"123"},
    @"3rd":@{@"name":@"rose",@"age":@"123"},
    @"sub":@{
        @"fourth":@{@"name":@"rose",@"age":@"123"},
        @"4th":@{@"name":@"rose",@"age":@"123"}}}
};
}

数据量并不大,可以看到最外层的数据包含了字符串,日期,数字,数组,词典这些常用的数据类型~KEY:`l`对应的@3456789映射到long类型的数据,`f`映射到float类型的数据,虽然目标是数字类型,但是json中究竟是什么格式并没有关系~

而日期方面,可以看出date1使用的是时间戳,而date2使用的是日期字符串~针对于日期数据的处理全部通过NESDateHandler来完成,坦白说这个东西已经可以分出来作为一个独立的日期工具存在了~写这东西的时候多少还是走了点心,不过这倒不是今天的重点~贴出这个类的测试用例就差不多包含了它的全部用法了~完整的测试用例和源码在最后都可以下载

-(void)testBlockHandler
{
    NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
    fmt.dateFormat = @"yyyy-MM-dd";
    NSDate *date1 = [NESDateHandler dateWith:@"date:2015-10-10" using:^NSDate *(id object) {
        NSString *dateString = [[object componentsSeparatedByString:@":"] lastObject];
        return [fmt dateFromString:dateString];
    }];
    NSDate *date2 = [fmt dateFromString:@"2015-10-10"];
    XCTAssertEqualObjects(date1, date2);
}
-(void)testCustomFormatter
{
    NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
    fmt.dateFormat = @"yyyy-MM-dd";
    [NESDateHandler setDateFormatter:fmt];
    NSDate *date1 = [NESDateHandler dateWith:@"2015-10-10 11:11:11"];
    NSDate *date2 = [NESDateHandler dateWith:@"2015"];
    //日期字符串不匹配,按照时间戳处理,两者得到的结果应该相同
    XCTAssertEqualObjects(date1, date2);
    date1 = [NESDateHandler dateWith:@"2015-10-10"];
    date2 = [fmt dateFromString:@"2015-10-10"];
    XCTAssertEqualObjects(date1, date2);
    [NESDateHandler setDateFormatter:[NESDateHandler defaultDateFormatter]];
}
-(void)testDefaultDateStringHandler
{
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:60*60*24*365*45 + 60*60*24 * 11];
    //45年+11个闰年+8个时区
    NSString *str = @"2015-01-01 08:00:00";
    NSString *target = [NESDateHandler stringWith:date];
    XCTAssertEqualObjects(str, target);
}
-(void)testCustomDateStringHandler
{
    NSString *str = @"自定义日期处理器";
    [NESDateHandler setDateStringHandler:^NSString *(NSDate * date) {
        return @"自定义日期处理器";
    }];
    NSString *target = [NESDateHandler stringWith:[NSDate date]];
    XCTAssertEqualObjects(str, target);
    [NESDateHandler setDateStringHandler:[NESDateHandler defaultDateStringHandler]];
}
-(void)testCustomDateHandler
{
    [NESDateHandler setDateHandler:^NSDate *(id object) {
        return [NSDate dateWithTimeIntervalSince1970:60*60*24*365*100];
    }];
    NSDate *date = [NSDate dateWithTimeIntervalSince1970:60*60*24*365*100];
    NSDate *target = [NESDateHandler dateWith:@"2015"];
    XCTAssertNotNil(target);
    XCTAssertEqualObjects(date, target);
    [NESDateHandler setDateHandler:[NESDateHandler defaultDateHandler]];
}
-(void)testDateFromInvalidateString
{
    NSDate *date = [NESDateHandler dateWith:@"a876yikdvhas"];
    NSDate *dateNow = [NSDate date];
    XCTAssertNotNil(date);
    NSTimeInterval t = dateNow.timeIntervalSince1970 - date.timeIntervalSince1970;
    XCTAssert(t > 0 && t < 0.001);
}
-(void)testDateFromTimeInterval
{
    NSDate *date1 = [NESDateHandler dateWith:@"2015/4/5 22:11:11.123"];
    NSDate *date2 = [NESDateHandler dateWith:@"2015"];
    XCTAssertNotNil(date1);
    XCTAssertNotNil(date2);
    XCTAssertEqualObjects(date1, date2);
}
-(void)testDateFromValidateString
{
    NSDate *date1 = [NESDateHandler dateWith:@"2014-4-5 22:10:19"];
    XCTAssertNotNil(date1);
}

关于日期字符串的处理倒是MJExtension没有的功能,也是我比较需要的一个东西~接下来就是针对上面的假数据应该如何定义模型了

#import <Foundation/Foundation.h>
#import "NESOtherModel.h"
#import "NESThirdModel.h"
#import "NESFourthModel.h"
@interface NESDemoModel : NSObject
@property (nonatomic,retain) NSString *name;
@property (nonatomic,retain) NSString *test_id;
@property (nonatomic,retain) NSString *code;
@property (nonatomic,retain) NESOtherModel *other;
@property (nonatomic,assign) int test;
@property (nonatomic,assign) double d;
@property (nonatomic,assign) long l;
@property (nonatomic,assign) float f;
@property (nonatomic,assign) BOOL b;
@property (nonatomic,retain) NSDate *date1;
@property (nonatomic,retain) NSDate *date2;
@property (nonatomic,retain) NSArray *other_arr;
@property (nonatomic,retain) NESThirdModel *_path_third;
@property (nonatomic,retain) NESThirdModel *_path_3rd;
@property (nonatomic,retain) NESFourthModel *_path_sub_fourth;
@property (nonatomic,retain) NESFourthModel *_path_sub_4th;
@property (nonatomic,assign) NSString * notExists_;
@end

没有任何协议,定义时不需要引入额外的头文件,直接继承自NSObject就好~属性的命名规则大体如下:

1.直接映射,属性名与json键值相同

2.间接映射,以字母开头,以`_`连接,前边为任意字符串,后边为json键,

例:@property (nonatomic,retain) NSString *test_id;对应json中的id

3.数组映射,对于数组类型的属性,用`_`连接模型名称和json键即可完成映射

例:@property (nonatomic,retain) NSArray *other_arr;对应json中的arr,至于用什么模型进行封装后文会说明

4.路径映射,当希望将json内部的某个对象直接映射成为模型属性的时候使用,以`_`开头,每一级路径以`_`连接

以json键作为结尾,路径映射可以包含多级路径

例:@property (nonatomic,retain) NESFourthModel *_path_sub_fourth;这里是二级路径映射,对照上边的数据源很容易理解

5.忽略属性,当需要在模型中定义一个json对象中没有的属性,同时在进行json字符串转化时将其忽略的属性时,直接在属性名最后加`_`

例:@property (nonatomic,assign) NSString * notExists_;这个就是在映射时不做任何操作的字段了

关于json对象内数组的映射,用到了一个叫做NESModelFormat的类,其中定义了全局的前缀和后缀,默认分别是空字符串和"Model",从头文件定义中看到引入了一个叫做`NESOtherModel`的类~这里`NES`就是前缀,需要在NESModelFormat中对齐进行手都配置,当然配置是全局的,只要在适当的位置配置一次就可以了~而这样`other_arr`的效果就是把json中arr对应的数组映射成为NESOtherModel类的对象组成的数组~实际使用的时候采用什么前缀什么后缀自己配置就是了~

映射完成后的对象反向生成的json字符串就是

{"b":"1","test":"3","f":"123.123","name":"jack","id":"123","code":"4567890-","other":{"attr3":"0.8","attr1":"value1","attr2":"150"},"d":"4.5","l":"3456789","date1":"2399-12-14 02:27:36","date2":"2015-04-03 11:18:24","arr":[{"attr3":"0.8","attr1":"value1","attr2":"150"},{"attr3":"0.8","attr1":"value1","attr2":"150"}],"path":{"third":{"age":"123","name":"rose"},"3rd":{"age":"123","name":"rose"},"sub":{"fourth":{"age":"123","name":"rose"},"4th":{"age":"123","name":"rose"}}}}

考虑到篇幅,就不加格式了~

而模型对象转词典的时候用了比较偷懒的写法...

 [NSJSONSerialization JSONObjectWithData:[self.jsonString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil];

到这里所有的功能也就出来了~所有的API定义在"NSObject+NESModel.h"中~也就三个方法

+(id)mappingWithObject:(id)object;
-(NSString *)jsonString;
-(id)foundationModel;

用起来大概是这个样子:

NESDemoModel *model = [NESDemoModel mappingWithObject:self.fakeData];
NSArray *arr = [NESDemoModel mappingWithObject:@[self.fakeData,self.fakeData,self.fakeData,self.fakeData]];
NSDictionary *dict = arr.foundationModel;

ok~大体就是这些内容了~还有考虑欠妥和功能不够完善的地方~欢迎大家提提意见,或者有什么尚未满足的需求需要添加也可以联系我~能力范围内的会尽快添加进来~

当然,这个毕竟是我个人比较喜欢的方式,简单总结一下,能够正确处理NSDate类型的属性,不需要在实现文件中写代码,属性明见名知意~如果你也喜欢这种方式欢迎使用这套工具~

如果对运行效率方面要求更高,那么还是强烈推荐使用MJExtension~

最后附上下载路径:

https://github.com/DominicNestor/NESModelExtDemo

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 17:19:53

让Json和Model相处的融洽点的相关文章

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

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

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

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或

用 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

C# Json转MODEL

所需DLL:Newtonsoft.Json.dll BaiDuTianQi _Model = JsonConvert.DeserializeObject<BaiDuTianQi>(jsonmsg); //某实体MODEL MODEL转JSON return Json(_BaiDuTianQi, JsonRequestBehavior.AllowGet);

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) { } } 二.在控制器

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

使用VS将 XML/Json 转为Model Class文件

环境: VS2015 Win10 XML例子: <OTA_GetRerStatusRQ EchoToken=" B3BB9248255BD851AC94" UserName="UserName" Password="Password " PrimaryLangID="en-us" TimeStamp="2015-05-2722:21:21" Version="1.000" >