Swift 和 JSON

Swift 和 JSON_第1张图片
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语言内在提供的功能和接口就可以让开发者写出的代码简洁和安全,而不依赖于第三方的代码库和框架。比如:

  1. 利用 ifguard 语句中的可选绑定和 **as? ** (类型转换操作符) , 解析JSON结构中已知类型的值为常量。
  2. 为了得到Dictionary, Swift可以把一个JSON对象有条件的地转换成[String: Any]
  3. 为了得到Array, Swift可以把一个JSON对象有条件的地转换成[Any]。如果已知数组内元素类型,如字符串类型数据可以转换为 [String]
  4. 如果需要获取字典里面的值,或数组内的值,可以通过枚举类型匹配或下标访问,然后结合可选类型转换绑定得到它 (结合第一条)。
// 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属性不做赘述。

Swift 和 JSON_第2张图片
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 = []
        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个技巧

更多

获取更多内容请关注微信公众号豆志昂扬:

  • 直接添加公众号豆志昂扬
  • 微信扫描下图二维码;
Swift 和 JSON_第3张图片

你可能感兴趣的:(Swift 和 JSON)