JSON
如果你的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语言内在提供的功能和接口就可以让开发者写出的代码简洁和安全,而不依赖于第三方的代码库和框架。比如:
// 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<Meal> }服务器返回的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<Meal> = [] 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属性不做赘述。
JSON
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<Meal> = [] 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. 反射在日常软件开发中,需要在不同的系统内传递数据,而转换格式的过程虽然是必须的,但也是单调无聊的。