前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动手写个 JSON-Model Mapping 库

动手写个 JSON-Model Mapping 库

作者头像
Sheepy
发布2018-09-10 12:19:53
1.2K0
发布2018-09-10 12:19:53
举报
文章被收录于专栏:我杨某人的青春满是悔恨

Swift 在 JSON解析方面有个比较有名的第三方库——SwiftyJSON,之前我也一直用的它。虽然用着还不错,但是它主要是为了避免手动解析 JSON 数据时大量的解包操作,降低解包不当导致 crash 的风险,感觉主要是注重安全性,易用性方面还是差了点。它支持下标操作,但毕竟是以字符串为键取值,IDE 不能自动补全,不仅麻烦还容易写错,而且用下标取到的值是JSON类型,一般还需要再进行类型转换,终究没有操作一个 Model 来得方便。所以我一直想写一个 JSON-Model 的映射器,我想要的效果是这样的:

  • 定义一个 Model :
代码语言:javascript
复制
class JSONModel {
    var error = ""
    var count = 0
    var posts = []
}
  • 发送网络请求后取得数据,然后直接转换成 JSONModel :
代码语言:javascript
复制
let jsonModel = data => JSOMModel.self

就这点需求,换个动态语言那根本不是事儿,哪怕在 C# 这样的静态语言中也能很简单地实现,只要用到反射特性就行了。然而 Swift 的反射实在是太弱了,运行期只能查看属性却不能给属性赋值。这简直无解,我读了一下 SwiftyJSON 的源码希望能找点灵感,然后发现 SwfityJSON 的流程是这样的(以直接解析 NSData 数据为例):

  • 以一个 NSData 类型的数据作为构造器参数实例化一个 JSON(一个 struct ),在构造器中调用 NSJSONSerialization.JSONObjectWithData(...)方法,如果 data 能被反序列化成一个 AnyObject 类型的对象的话,就调用另一个构造器,把这个对象赋值给实例属性 object ,否则就给object赋一个NSNull()
  • object是一个计算属性,在给它赋值时,会对它的类型进行判断,然后把它的类型信息存储到实例属性type中(type是一个自定义的枚举类型,这个枚举类型基本对应了 Swift 中的几种基本类型),最后把object的值进行类型转化后赋值给JSON中的一个特定类型的私有属性,譬如是数组的话就赋值给rawArray,是字符串的话就赋值给rawString,等等。
  • 在获取object时会先判断实例属性type,根据type的值返回对应的 rawValue,譬如 type == .String的话,就返回 rawString。然后像arrayarrayValue这样的都是计算属性,array的话会先去判断type是不是.Array,是就返回rawArray,否则返回nil,而arrayValue不会返回nil,若类型不匹配则返回一个空数组[]

别的当然还有一些内容,譬如自定义下标,实现各种协议(字符串字面量协议、判等协议、比较协议、打印协议等等),代码很优雅,但似乎没有我想要的东西。

最终我觉得,用 Swfit 的原生语法应该是办不到了,只能借助于 OC 的 runtime。主要是要用到 KVC,这样一来所有的 Model 都得继承自 NSObject。我写了个 Demo,从联网获取数据到显示数据的整个流程如下:

  • 先看看JSON数据的结构:

JSON.png

  • 定义两个Model:
代码语言:javascript
复制
class JSONModel: NSObject {
    var error = ""
    var count = 0
    var posts = []
}
class PostModel: NSObject {
    var id = 0
    var date = NSDate()
    var name = ""
    var pic = ""
    var publishtime = ""
    var count = 0
    var excerpt = ""
}
  • 发送网络请求(你可以使用 Alamore 或别的什么库,我这边是自己简单封装了一下 NSURLSession 直接用了),然后将取得的数据先转化成 JSONModel(直接使用=>符号),保存到实例属性jsonModel中:
代码语言:javascript
复制
getDataFromUrl(Constant.DemoAPI, method: .GET, parameter: nil) { data, error in
    if let jsonData = data {
        self.jsonModel = jsonData => JSONModel.self
    }
    
    if let httpError = error {
        print(httpError)
    }
}
  • jsonModel一旦被赋值就会刷新tableView,看一下配置cell的方法:
代码语言:javascript
复制
func configCell(cell: UITableViewCell, indexPath: NSIndexPath) -> UITableViewCell {
    
    if let model = jsonModel, post = model.posts[indexPath.section] => PostModel.self {
        cell.textLabel?.text = post.excerpt
    }
    
    return cell
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(Constant.ReuseIdentifier, forIndexPath: indexPath)
    
    // Configure the cell...
    return configCell(cell, indexPath: indexPath)
    
}

也是用=>直接把posts数组中的元素都转化为PostModel类型的实例了,然后直接cell.textLabel?.text = post.excerpt,就把我们想显示的内容放到cell里了。

显示数据.png

使用就是这么简单,只要新建一个NSObject的子类,属性名保证跟 JSON 中的一致,并给各个属性一个初始值。如果想另取属性名也是可以的,用计算属性就好了,譬如 Demo 中 publishtime 是不符合 Swift 属性命名规范的,我们不去改原 Model,而是用一个扩展:

代码语言:javascript
复制
extension PostModel {
    var publishTime: String {
        return publishtime
    }
}

这虽然不是很完美(因为publishtime还在),但也凑合能用了。

转换器主要是用到了反射( Mirror 实现)和 KVC ,代码就不贴了,大家可以去 Github直接看源码,clone 下来跑一下 Demo 看看。要用到自己的项目中的话直接把 JSONModelMapper.swift文件或者连同HTTPManager.swift一起拖到项目中好了,因为实在“超轻量级”(简陋……),我觉得这样最方便了。

觉得还算有用的话随手点个 Star 可好……当然有什么意见或建议的话欢迎指教。源码在这里

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015.12.24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档