如果你的Swift应用程序需要和网络服务器进行交互,并且返回的数据格式是JSON,这时可以使用基础框架提供的 JSONSerialization 类,通过它可以把JSON 转换成Swift 支持的基础数据类型,如 Dictionary, Array, String, Number,和Bool。
但是有时候你不能确定客户端收到的JSON结构和值总是一成不变的,这时候成功地反序列化数据流,健壮地生成对象会变的有些许挑战性,这里分享一些处理JSON的思路,希望能对大家有所帮助。
1. 从JSON中取值
JSONSerialization 类中方法jsonObject(with:options:) 返回 Any 类型的值并在解析失败时抛出错误异常。
import Foundation
let data: Data // 通过网络请求获取的数据
let json = try? JSONSerialization.jsonObject(with: data, options: [])
在解析具体JSON的过程中,Swift语言内在提供的功能和接口就可以让开发者写出的代码简洁和安全,而不依赖于第三方的代码库和框架。比如:
- 利用 if 或 guard 语句中的可选绑定和 **as? ** (类型转换操作符) , 解析JSON结构中已知类型的值为常量。
- 为了得到Dictionary, Swift可以把一个JSON对象有条件的地转换成[String: Any]。
- 为了得到Array, Swift可以把一个JSON对象有条件的地转换成[Any]。如果已知数组内元素类型,如字符串类型数据可以转换为 [String]。
- 如果需要获取字典里面的值,或数组内的值,可以通过枚举类型匹配或下标访问,然后结合可选类型转换绑定得到它 (结合第一条)。
// JSON 样例1
/*
{
"someKey": 42.0,
"anotherKey": {
"someNestedKey": true
}
}
*/
if let dictionary = jsonWithObjectRoot as? [String: Any] {
if let number = dictionary["someKey"] as? Double {
// 获取字典中的值
}
for (key, value) in dictionary {
// 成对地获取字典中的所有的 主键 和 值
}
if let nestedDictionary = dictionary["anotherKey"] as? [String: Any] {
// 访问嵌套式字典
}
}
// JSON 样例2
/*
[
"hello", 3, true
]
*/
if let array = jsonWithArrayRoot as? [Any] {
if let firstObject = array.first {
// 访问数组内的单个值
}
for object in array {
// 访问数组内的所有数组
}
for case let string as String in array {
// 访问数组内所有类型为String的值
}
}
2. 利用JSON抽取的值生成对象
因为绝大部分Swift应用都在使用 Model-View-Controller 设计模式, 在Model 定义范围内,会经常用到把JSON数据转换为对象。
例如,你的应用需要提供本地酒店搜索功能,你需要定义一个 Restaurant模型,模型里有一个接受JSON对象的初始化方法,还有一个发送网络请求的方法,该网络请求可以异步以数组形式返回酒店信息。
一般酒店应包括酒店名称(String),地址(经纬度)(Tuple)和 餐饮(Set),酒店模型代码如下:
import Foundation
struct Restaurant {
enum Meal: String {
case breakfast, lunch, dinner
}
let name: String
let location: (latitude: Double, longitude: Double)
let meals: Set
}
服务器返回的JSON数据样本:
{
"name": "Caffè Macs",
"coordinates": {
"lat": 37.330576,
"lng": -122.029739
},
"meals": ["breakfast", "lunch", "dinner"]
}
这时我们就要思考如何实现把JSON数据转换成 Restaurant 对象,在初始化方法里把Any类型的值转换成Restaurant对象中不同类型的属性。
extension Restaurant {
init?(json: [String: Any]) {
guard let name = json["name"] as? String,
let coordinatesJSON = json["coordinates"] as? [String: Double],
let latitude = coordinatesJSON["lat"],
let longitude = coordinatesJSON["lng"],
let mealsJSON = json["meals"] as? [String]
else {
return nil
}
var meals: Set = []
for string in mealsJSON {
guard let meal = Meal(rawValue: string) else {
return nil
}
meals.insert(meal)
}
self.name = name
self.location = (latitude, longitude)
self.meals = meals
}
}
结合1. 从JSON中取值 和 上面代码样例,我们可以看到所有从JSON字典获取的值都通过可选类型转换绑定转换成常量,如 酒店名称的值被直接赋值给 name属性,而经纬度的值结合成元组后赋值给location属性,meals属性不做赘述。
3. 错误/异常 处理
在上面的代码样例的可选初始化方法中,如果反序列化数据失败将返回nil, 更进一步,我们可以扩展Error协议,当反序列化失败的时候抛出异常,而不是返回 nil,代码样例如下:
enum SerializationError: Error {
case missing(String)
case invalid(String, Any)
}
extension Restaurant {
init(json: [String: Any]) throws {
// 提取酒店名
guard let name = json["name"] as? String else {
throw SerializationError.missing("酒店名缺失")
}
// 提取经纬度
guard let coordinatesJSON = json["coordinates"] as? [String: Double],
let latitude = coordinatesJSON["lat"],
let longitude = coordinatesJSON["lng"]
else {
throw SerializationError.missing("经纬度缺失")
}
// 校验经纬度是否有效
let coordinates = (latitude, longitude)
guard case (-90...90, -180...180) = coordinates else {
throw SerializationError.invalid("经纬度格式无效的", coordinates)
}
// 提取餐饮并校验是否有效
guard let mealsJSON = json["meals"] as? [String] else {
throw SerializationError.missing("餐饮信息缺失")
}
var meals: Set = []
for string in mealsJSON {
guard let meal = Meal(rawValue: string) else {
throw SerializationError.invalid("餐饮信息无效", string)
}
meals.insert(meal)
}
// 初始化属性
self.name = name
self.coordinates = coordinates
self.meals = meals
}
}
4. 反射
在日常软件开发中,需要在不同的系统内传递数据,而转换格式的过程虽然是必须的,但也是单调无聊的。
由于数据在不同系统内结构也有可能类似,我们可以尝试自动匹配两者子元素的关系,比如,服务器返回数据命名遵守蛇底式小写法( snake_case),而客户端命名遵守使用驼峰式大小写法( CamelCase), 这是可以利用Swift中的 反射API Mirror 自动把JSON转换成代码可直接使用的模型(Model)。
但是,善于思考的你可能发现这种使反射语法带来的正面效益有限,反而让代码调试变得困难,一些边缘问题也更难处理。在上面的例子中,我们不仅提取映射JSON数据,还初始化了复杂数据类型和输入数据的校验。如果上述代码使用反射将会变得更加复杂,不易理解。 在日常项目中评估可行方案时,如果策略错误可能得不偿失,为将来埋下技术债的隐患。
事实上一些成熟的开源库可以帮你轻松地处理JSON, 如iOS开发第三方库一 ObjectMapper
推荐阅读:
Swift高手进阶 - 10个技巧
更多
获取更多内容请关注微信公众号豆志昂扬:
- 直接添加公众号豆志昂扬;
- 微信扫描下图二维码;