swift中优雅的处理JSON

声明:本文中部分引用了喵神博客中关于JSON处理的举例

在iOS开发中,只要你的APP和网络打交道,那么基本上处理JSON是必须的。

在Objective - C里面处理JSON就非常方便了,你可以随意的使用各方大神的开源库(YYModel,Mantle,MJExtension...)来处理,整个过程无痛无氧,顺滑如丝,因为Objective - C可以使用runtime非常自由。

举个例子:

// jsonString ------ 引用自喵神的博客
{"menu": {
    "id": "file",
    "value": "File",
    "popup": {
        "menuitem": [
            {"value": "New", "onclick": "CreateNewDoc()"},
            {"value": "Open", "onclick": "OpenDoc()"},
            {"value": "Close", "onclick": "CloseDoc()"}
        ]
    }
}}

在Objective - C,如果你使用开源库来解析,非常简单,一句话:

Menu *menu = [Menu yy_modelWithJSON:json];

这么一句话,不但帮你把复杂的层级关系解析好了,而且做好对应model的映射赋值,这功能简直了,那么我们看看在swift中是如何处理的,代码如下:

// 访问menuitem中的某一个value的值 ------ 引用自喵神的博客
if let jsonDic = json as? NSDictionary,
          menu = jsonDic["menu"] as? [String: AnyObject],
         popup = menu["popup"],
      popupDic = popup as? [String: AnyObject],
     menuItems = popupDic["menuitem"],
  menuItemsArr = menuItems as? [AnyObject],
         item0 = menuItemsArr[0] as? [String: AnyObject],
         value = item0["value"]
{
    print(value)
}

有童鞋可能会觉得,这还好呀,代码整齐,格式美观

可是真的是这样么?难道你家的json都是这么短的 ?我想你此刻已经知道我要说啥了,对,就是那个该死的后台,一眼看去望不到边的json,随便往工具里面转化一个,差不多两屏…

咱们打死他可好?

且慢!

当然啦,在swift也有相应的JSON解析开源库可用,比如SwiftyJSON,而且看起来非常6,乍一看简直上天,来来来,你们感受一下

// 使用 SwiftJSON ------ 引用自喵神的博客
if let value = JSON(json)["menu"]["popup"]["menuitem"][0]["value"].string {
    print(value)
}

屌炸天有木有,自由的无限引用。有些同学可能会瞬间想到一个问题:我靠,这样以后是不是不用写model了?哪里需要,哪里就会出现JSON["xxx”]。

这个问题,很多人讨论过,这样看起来没有任何问题,反正可以取到json值,怕个鸟,写呗。

但是这样真的好么?没有了model,维护成本真的是成倍增加的,你让后来的维护同学怎么玩?再去撸一遍后台同学的开发文档?那个能把json写成几页长度的童鞋写出来的文档,你确定他能看下去?就算看下去了,你确定能看懂?就算看懂了,你确定他不会骂你?

好吧,还是乖乖的写Model吧。

那么问题来了,写了model,但是SwiftyJSON这货只能把json解析出来,不能自动映射到你的model实例上去。什么?你没有听懂!

呃...

好吧,是这样的,比如:

let model = TestModel() // 如果想让这个model里面有东西,你还得赋值/初始化

// 初始化/赋值
model.a = JSON["a"]
model.b = JSON["b"]
model.c = JSON["c"]
model.d = JSON["d"]
... // 以下省略两页纸

我的天呐...

要死人么 ?


为了这个问题,我也曾苦恼过,我也曾痛苦过…

在github上搜索了N久,也问过各路大神(有些大神同学居然有setValueForKey,好吧,也是个方法).

后来找到一个可以自动匹配的库ObjectMapper,终于春天来临了...

ObjectMapper使用大致如下:

class User: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var array: [AnyObject]?
    var dictionary: [String : AnyObject] = [:]
    var bestFriend: User?                       // Nested User object
    var friends: [User]?                        // Array of Users
    var birthday: NSDate?

    required init?(_ map: Map) {

    }

    // Mappable
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        array       <- map["arr"]
        dictionary  <- map["dict"]
        bestFriend  <- map["best_friend"]
        friends     <- map["friends"]
        birthday    <- (map["birthday"], DateTransform())
    }
}

struct Temperature: Mappable {
    var celsius: Double?
    var fahrenheit: Double?

    init?(_ map: Map) {

    }

    mutating func mapping(map: Map) {
        celsius     <- map["celsius"]
        fahrenheit  <- map["fahrenheit"]
    }
}

// 然后这样一句话就可以自动映射了
let user = Mapper<User>().map(JSONString)

其实,比起YYModel等库,还是麻烦了不少,程序员嘛,总会有办法。

后来我发现了一个好东西,SwiftyJSONAccelerator,这家伙有点儿类似以前的一个Xcode插件ESJsonFormator,对,你把自己的json串放进去,他能自动给你生成model,并且各中匹配都给你写好自动生成

SwiftyJSONAccelerator

哇偶,感觉瞬间翻身解放了.

等等,并未解放,ObjectMapper还有一个蛋疼的问题。

那就是,这个东西,你必须把整个json都写好,然后让他去匹配,那如果只需要一部分怎么办?

比如,外围的数据其实不需要,我们只要body或者data字段下的东西...

我的天呐,简直有点儿傻那啥(你们说的哈,我没有说…)

为了解决这个问题,我github上各种搜,未果…

后来只能乖乖的回去研究他的源码,看看能不能改。

然而并没有,后来在AlamofireObjectMapper里面发现提供了一个keyPath的方法:

// 使用了AlamofireObjectMapper以后,你可以这样玩
Alamofire.request(.GET, URL).responseObject(keyPath: "body") { (response: Response<WeatherResponse, NSError>) in

    let weatherResponse = response.result.value
    print(weatherResponse?.location)

    if let threeDayForecast = weatherResponse?.threeDayForecast {
        for forecast in threeDayForecast {
            print(forecast.day)
            print(forecast.temperature)           
        }
    }
}

哈哈,这样就好多了,很多不需要的外围字段,可以过滤掉。

但,这样感觉相对没有SwiftyJSON那种牛逼烘烘的自由度了,于是又开始各种折腾。

SwiftyJSON里面有提供这样API:

// 通过这个API,你可以把你得到的JSON转换成源生的jsonString
public func rawString(encoding: UInt = NSUTF8StringEncoding, options opt: NSJSONWritingOptions = .PrettyPrinted) -> String?

有的小伙伴估计已经想到了,对的

SwiftyJSONObjectMapper组合起来,使用SwiftyJSON取到任何自己想取的数据字段,然后去map到Model上去,是不是就完美了!

所以网络请求就封装成了下面这样:

func postForObject<T: Mappable>(objectType type: T.Type, 
                                           path: String, 
                                     parameters: [String : AnyObject]?, 
                                       finished: (result: T?, error: NSError? ) -> Void) {
        Alamofire.request(.POST, baseUrl + path, parameters: parameters).responseJSON { response in
            let json = JSON(data: response.data!)
            if json["status"].int == 200 {
                if let error = json["body"].error {
                    finished(result: nil, error: error)
                } else {
                    let rawJson = json["body"].rawString(NSUTF8StringEncoding, options: .PrettyPrinted)
                    let obj = Mapper<T>().map(rawJson!)
                    finished(result: obj, error: nil)
                }
            } else {
                finished(result: nil, error: nil)
            }
        }
    }

如果有一些model数据量很小,你都不想写model了,你也可以再开放一个纯JSON的API,大致如下:

func postForJSON(path: String, parameters: [String : AnyObject]?, finished: (result: JSON?, error: NSError?) -> Void) {
        Alamofire.request(.POST, baseUrl + path, parameters: parameters).responseJSON { response in
            let json = JSON(data: response.data!)
            if json["status"].int == 200 {
                if let error = json["body"].error {
                    finished(result: nil, error: error)
                } else {
                    finished(result: json["body"], error: nil)
                }
            } else {
                finished(result: nil, error: nil)
            }
        }
    }

如果错误欢迎讨论...
生命不息,折腾不止...
I'm not a real coder,but i love it so much!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容