Swift 解析 Json 的方式足以让任何代码段看起来就像一坨米田共……这不是在开玩笑,因为你真的是一层一层堆上去的。
好在,程序员总是有办法实现自己的优雅。JSONModel, YYModel 等类库都是非常好用的 Json 解析库。可以直接把 Json 数据解析成对象。这样就可以在工作中非常好的使用了。
Swift 在这方面有先天的不足,因为所有的这些实现都离不开 KeyValue 的使用。但是说实话,非必要情况下,我是真不愿让我的模型继承自 NSObject. 毕竟那是非常 OC 的东西。再加上我有一个非常让人烦躁的工作环境,写服务器的人三天两头的变更数据结构。而且它还很喜欢对象套对象,这就让使用这些库的安卓同事很无奈了。这意味着他要经常新增对象,修改属性名称。
在偶然的情况下,我看到一个对于 Json 解析的类库,并不是把它解析成模型,而是通过下标的方式直接读取 Json 内容。如:
var some = json["value", "some"].string
很遗憾的是当时我没有把这个类库给 Git 下来,但是其实想想,这样的实现可以非常的简单。于是,我又造了个轮子。
背景知识
JSONSerialization
- 将 Json 数据转换成字典
JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
- 将字典转换成 Json
/// 这段代码是在 Swift 2 的环境下截取的,我稍微做了点修改,让他看起来更像 Swift 3... 总之,有时间改一下吧。
JSONSerialization.data(json, options: NSJSONWritingOptions.PrettyPrinted)
Subscrpts 下标
下标的语法很简单,是 Swift 当中非常华丽的功能。
subscript(index: Int) -> Int {
get {
// return an appropriate subscript value here
}
set(newValue) {
// perform a suitable setting action here
}
}
Apple: The Swift Programming Language (Swift 3) -> Language Guide -> Subscrpts
思考
首先假设我们清晰的知道完整的 Json 数据结构。这应该是必要的条件。
{
value: {
int: {
10,
20,
30
},
string: "Some String",
date: "2016-09-30"
}
}
那么,最终我希望能拿到数据应该是类似
json["value", "int", 1].int
json 为数据对象,[] 下标当中放置的是完整的读取路径,表示 value 字段下的 int 字段里的第一个元素。后面表示将它读成 int 类型。
所以关键点就是
- 数据存储
- 解析链条
- 类型指定
另外需要考虑两个错误情况
- 链条错误,字段并不存在
- 类型错误
解决方法其实很简单,假如链条错误,那就返回 nil 即可。类型错误可以考虑两种情况,一种是即使错误也能读取一个默认值,一种是读取错误返回 nil。(至少我是不希望读取错误就直接把程序中断了,谁知道会不会在程序上线后服务器改了一个字段大小写呢……又不是没遇到过。)
因此对于直接指定类型的如 int, string 这种返回的应该是 option 类型。并提供一个方法,可以指定类型的同时指定默认值,如果类型错误就返回默认值。
实现代码如下,只要用泛型即可解决。会自动根据所给的 null 参数判断所需要的类型,然后进行类型判断。
func type<T>(null: T) -> T {
if let v = result as? T {
result = json
return v
} else {
result = json
return null
}
}
实现
这么简单的类,这个类其实早已经实现好,并拿我自己的项目做过小白鼠了,所以我就不写伪代码了。
//
// Json.swift
// Json
//
// Created by 黄穆斌 on 16/9/25.
// Copyright © 2016年 MuBinHuang. All rights reserved.
//
import Foundation
// MARK: - Json Data
class Json {
// MARK: Tmp Data
var json: Any? { didSet { result = json } }
var result: Any?
// MARK: Init
init(_ data: Data?) {
if let data = data {
if let json = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) {
self.json = json
self.result = json
}
}
}
init(_ json: Any) {
self.json = json
self.result = json
}
// MARK: Subscript
subscript(keys: Any...) -> Json {
var tmp: Any? = result
for (i, key) in keys.enumerated() {
if i == keys.count - 1 {
if let data = tmp as? [String: Any], let v = key as? String {
result = data[v]
continue
} else if let data = tmp as? [Any], let v = key as? Int {
if v < data.count {
result = data[v]
continue
}
}
} else {
if let data = tmp as? [String: Any], let v = key as? String {
tmp = data[v]
continue
} else if let data = tmp as? [Any], let v = key as? Int {
if v < data.count {
tmp = data[v]
continue
}
}
}
tmp = nil
}
return self
}
// MARK: Types
var int: Int? {
let value = result as? Int
result = json
return value
}
var double: Double? {
let value = result as? Double
result = json
return value
}
var string: String? {
let value = result as? String
result = json
return value
}
var date: Date? {
if let value = result as? Double {
result = json
return Date(timeIntervalSince1970: value)
} else {
result = json
return nil
}
}
var array: [Json] {
if let value = result as? [AnyObject] {
result = json
var arr = [Json]()
for v in value {
arr.append(Json(v))
}
return arr
}
result = json
return []
}
var dictionary: [String: Json] {
if var value = result as? [String: AnyObject] {
result = json
for (k, v) in value {
value[k] = Json(v)
}
return value as! [String: Json]
}
result = json
return [:]
}
func type<T>(null: T) -> T {
if let v = result as? T {
result = json
return v
} else {
result = json
return null
}
}
func date(_ format: String) -> String {
if let value = result as? Double {
result = json
let form = DateFormatter()
form.dateFormat = format
return form.string(from: Date(timeIntervalSince1970: value))
} else {
result = json
return ""
}
}
// MARK: Methods
func value(key: String, _ null: String = "") -> String {
if let dic = result as? [String: Any] {
if let value = dic[key] as? String {
result = json
return value
}
}
result = json
return null
}
func value<T>(key: String, _ null: T) -> T {
if let dic = json as? [String: Any] {
if let value = dic[key] as? T {
result = json
return value
}
}
result = json
return null
}
}
// MARK: - Json Protocol
protocol JsonProtocol {
func jsonToModel(json: Json)
}
总结
在类的最后,我写了一个协议,定义了一个 jsonToModel 的方法。事实上,这个协议并没有什么意义,关键是要提供我自己,在我写的模型中,需要加入这个协议,然后在里面实现 json 数据与类属性之间的对应关系。这一块代码,实际上应该是属于模型自己本身做的事情。不应该把这个解析过程放在网络实现当中。
- 网络类:负责网络传输。
- 模型类:负责数据处理。