SCJSONUtil
功能
- 小巧,方便,功能强大
- 支持自定义属性名(例如服务器返回的是id,我们的model中可以定义为uid)
- 支持类型自动匹配(例如服务器返回的是Number,model中定义的是String,那么将解析为 String)
在 2.1 之前该框架会将服务器返回的字段全部转换为 NSString 类型,因此model的头文件中定义的都是 NSString 类型,这对手动使用者来说是个恶心的限制,一些用户和我的团队都给我反馈过这个问题,因此我决定支持下类型自动匹配的功能,因此更加完美了!
使用 CocoaPods 安装
在 Podfile
文件中添加
target 'TargetName' do
pod 'SCJSONUtil'
end
使用说明
“一看二建三解析”,直接看代码比较直观:
- 先看 JOSN,因为我们要把 JSON 转为 Model 嘛
{
"code": "0",
"content": {
"gallery": [
{
"isFlagship": "0",
"name": "白色情人节 与浪漫牵手",
"pic": "http://pic16.shangpin.com/e/s/15/03/06/20150306174649601525-10-10.jpg",
"refContent": "http://m.shangpin.com/meet/189",
"type": "5"
},
...
]
}
}
- 根据服务器的json(嵌套关系)建立 model (子model);
@interface GalleryModel : NSObject
@property (nonatomic, copy) NSString *isFlagship;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *pic;
@property (nonatomic, copy) NSString *refContent;
@property (nonatomic, copy) NSString *type;
@end
- 使用JSONUtil解析
假设responseJSON是服务器返回的json数据,那么正规的写法是先判断 code 是否等于 0,然后再取出 gallery 对应的json,然后交给 JSONUtil !JSONUtil提供了便捷的方法能够方便将它取出来:
//1.根据 keypath 取出目标json
id findedJSON = SCFindJSONwithKeyPath(@"content/gallery", responseJSON);
//2.使用 JSONUtil 解析
NSArray *models = SCJSON2Model(findedJSON, @"GalleryModel");
//models 就是你想要的GalleryModel数组了!
其他用法,请参考demo。
核心思想
- 递归,因为 JSON 可以嵌套,因此这是一个递归问题;
- 遍历 JSON,而不是遍历 model
- 在适当的地方进行 ValueTransfer,实现自动类型匹配
- 通过 kvc 给 model 赋值
核心思想应该是相同的,不过具体实现差异确实很大!
主流 JSON 转 Model 流程
这里需要解释第二点,因为这里我和其他 JSON 转 Model 框架的实现有很大不同!先来看看其他主流的思想:
- 使用 Runtime 获取目标 Model 类的所有属性!
- 注意这里只能获取到自身的属性,父类的则无法获取到!考虑到继承是我们的一大特性,不应该限制 Model 类不能有父类(除了 NSObject 基类外),因此需要使用 superClass 向上遍历,并且遍历到系统类为止,否则可能会获取到一些不想要的属性,可能会给后续工作带来不必要的麻烦。
- 将上一步获取到的所有属性缓存到一个全局区域,保证下次能够走缓存!
- 因为解析时,往往都是给出一个类,而不是一个对象,因此缓存需要跟类挂钩,比如定义一个字典,将类名作为 key 值,value 则是需要缓存的属性;具体实现也很简单,我曾经写过一篇剖析关联引用底层的文章,系统的做法跟这个类似,其实就是通过静态变量来做的,然后可以提供类方法访问,类似于 Java 的类变量。
- 这里所说的属性,一般包括属性名,属性类型等字段。
- 拿到 Model 类的全部属性后开始遍历
- 在遍历的时候,判断一些全局的忽略设置,值的转换处理等。
- 如果值是字典或数组,则会递归。
JSONUtil 转 Model 流程
- 遍历服务器返回的 JSON
- 根据遍历的 key,去 model 中查询该属性
- 如果找到了,说明 model 中定义了该属性,就通过 Runtime 取出属性的类型,解析继续往下走
- 如果没有找到,说明 model 中不关心该属性,开始下次遍历
- 如果值是数组或字典则开始递归
- 通过第二步获取的属性类型和 JSON 值类型比较
- 如果类型一致,则直接 kvc 赋值
- 如果类型不一致,则先进行转换,然后再 kvc 赋值
JSONUtil 与主流转换框架的区别
可以看出: JSONUtil 遍历的是服务器返回的 JSON,而不是 model 的所有属性,并且没有做 cache!这是与其他框架有很大区别!
JSONUtil 不做 cache 的原因
知道了主流做法后,再来看看 JSONUtil 是如何处理的吧,你会发现 JSONUtil 有很大特色,因为随大流地去做 cache,遍历 model 的所有属性,之所以没有这么做,是因为我认为 没有必要!
-
做 cache 是为了 下一次 解析更快,你知道将一个 JSON 转换出一个 model 需要多久吗?对于普通的 JSON,其实不用担心的,如果真的担心,何不在异步线程里使用 GCD 做异步解析呢?试问你愿意多等 1 秒(异步 GCD 解析)还是愿意让 App 停顿 0.5 秒(主线程解析)?
-
举个例子:如果 model 是一个 3G 开关,这个开关只会在 App 启动时请求一次,那么 cache 反而没什么用,甚至可能会更耗时!
-
再比如我的解析框架做了 cache,解析的速度非常快,你以为你就敢大胆地在主线程进行所有的 JSON 解析吗?你就不怕有大块 JSON 吗?
-
如果做了 cache,并且在异步线程解析,难道不会更快吗?是的,确实会快一些,那个差别你可能感觉不到,为了那种你感觉不到的快捷而去浪费时间层层遍历属性来做 cache,这是不值得的,你是否同意呢?
这是我做的测试:
NSDictionary *userInfoDic = [self readUserInfo];
[self testCount:100000 work:^{
UserInfoModel *uModel = [UserInfoModel instanceFormDic:userInfoDic];
}];
// 10000 次转换耗时:0.51412s
// 100000 次转换耗时:4.61152s
也就是说使用 JSONUtil 转换一个嵌套 4 层(里面也有数组)的 model,转换一次需要: 0.0514ms.
为什么 JSONUtil 选择遍历服务器返回的 JSON?
遍历终究是要做的,有两种做法,一种是遍历 model的所有属性,另一种是遍历 服务器返回的json;究竟如何选择,困扰了我许久,最后我选择了后者,因为前者的代价较高,需要层层向上遍历,最可恶的是递归的出口不好把握,因此选择了后者规避了这个问题!
另一方面选择后者是因为,根据我解析的习惯是,服务器定义的字段我都会解析,但是有的字段服务器可能不返回,如果按照前者的遍历方式,会增加几次多余的遍历;当然我否定使用后者不会出现多余的遍历,这个定义model的习惯和服务器返回json的空值都有关系!
版本
-
1.0 必须继承 QLBaseModel 父类;
-
1.0.1 在属性命名上有限制,对于子model必须故意制造出"冲突 key";
-
1.0.2 去掉了必须制造冲突 key 的限制,使用起来更方便;
-
2.0 则去掉了必须继承父类的限制,更加贴合实际应用;QLJSON2Model 改名为 JSONUtil!
-
2.1 自动匹配类型,比如服务器返回了一个Number,客户端model属性是String,那么框架会帮你自动转为String!
-
2.2 公司项目也使用这个库,因此遵循内部的命名规范,统一加上sl(SL)前缀!
-
2.3 公司内部工程重构,将该库提取到了通用库中,因此修改了类名将 SL 改为了 SC ,以便日后尽快更新该库!
-
2.4 支持 CocoaPods 安装;demo使用pods!
-
2.4.1 清理没用的方法
-
2.4.2 当服务器返回数据不能转化为Number时,不能使用KVC赋值。
-
2.4.3 开始在 OS X 平台测试使用。
-
2.4.4 增删类别方法,增加警告信息,创建Model更加方便。
做了类型自动映射后,sc_valueNeedTransfer 显得鸡肋,因此去掉了 - (void)sc_valueNeedTransfer; 方法。 新增 - (id)sc_key:(NSString *)key beforeAssignedValue:(id)value; //增加警告信息 2018-11-27 18:30:17.844219+0800 SCJSONUtilDemo[91682:1947248] ⚠️ UserInfoModel 类没有解析 test 属性;如果客户端和服务端key不相同可通过sc_collideKeysMap 做映射!value:testValue 2018-11-27 18:30:17.844351+0800 SCJSONUtilDemo[91682:1947248] ⚠️⚠️ DataInfoModel 类的 cars 属性没有指定model类名,这会导致解析后数组里的值是原始值,并非model对象!可以通过 sc_collideKeyModelMap 指定 @{@"cars":@"XyzModel"}
-
2.4.5 增加类别方法,可动态做key-value映射。
-
2.4.6 增加类别方法,可自定义解析过程。
-
2.4.7 修复通过 KVC 给标量赋值为 nil 时导致的崩溃。
-
2.4.8 修复没有配置Model名时,解析完毕后,属性值为nil问题。
-
2.4.9 当服务器返回值是数组类型,而Model属性不是数组类型时,不进行解析。
-
2.5.0 支持解析 long long 类型,丰富测试 case。
-
2.5.1 支持 fileURL 类型,丰富测试 case。
-
2.5.2 增加日志控制开关,更加灵活。
-
2.5.3 重构解析过程,支持keypath映射。
-
2.5.4 支持 Model 转 json;提供 JSON2String 方法。
-
2.5.5 修复服务端返回字段是 description,hash等只读属性字段时的崩溃。