阅读 1148
Swift:JSON解析(下)

Swift:JSON解析(下)

这是我参与更文挑战的第16天,活动详情查看: 更文挑战

本篇主要讲解通过Codable协议对JSON进行解析

如何进行JSON解析(续)

Swift4.0之后——黄金时代

从一个简单的例子开始

Swift4,官方团队推出了Codable协议,直接就将JSON转Model的开发体验上升了不止一个台阶。

Codable的协议的易用性、傻瓜操作性,直接将网络请求这部分Model的定义的工作量几乎降到了0。

我们通过下面这个例子来说明一下。

需要解析的JSON:

{
    "ret_code":"0",
    "ret_msg":"success",
    "timestamp":"2021-06-04 11:36:55",
    "response_data":{
        "access_token":"some token",
        "expires_in":86400,
        "refresh_token_expires_in":2592000,
        "refresh_token":"some refresh token"
    }
}
复制代码

转换为Model:

import Foundation

struct Token : Codable {

    let retCode: String?
    let retMsg: String?
    let timestamp: String?
    let responseData: TokenData?

    enum CodingKeys: String, CodingKey {
        case retCode = "ret_code"
        case retMsg = "ret_msg"
        case timestamp
        case responseData = "response_data"
    }
}

struct TokenData : Codable {

    let accessToken: String?
    let expiresIn: Int?
    let refreshTokenExpiresIn: Int?
    let refreshToken: String?

    enum CodingKeys: String, CodingKey {
        case accessToken = "access_token"
        case expiresIn = "expires_in"
        case refreshTokenExpiresIn = "refresh_token_expires_in"
        case refreshToken = "refresh_token"
    }
}
复制代码

这段JSON后端典型了使用了蛇形命名法对属性进行命名,而驼峰命名才是Swift的风格,不过没有关系,通过工具类软件或者App,这些帮我做好了。

通过CodingKey协议,将蛇形命名向驼峰命名转换。

如果你不想将蛇形改为驼峰,只是想单纯的转换,那么上面的每个struct的enum CodingKeys: String, CodingKey都可以省去了,变得更为简单:

struct Token : Codable {

    let ret_code: String?
    let ret_msg: String?
    let timestamp: String?
    let response_data: TokenData?
    
}

struct TokenData : Codable {

    let access_token: String?
    let expires_in: Int?
    let refresh_token_expires_in: Int?
    let refresh_token: String?
}
复制代码

每个结构体或者类遵守Codable,写出JSON的属性与类型,就可以了。

Model定义的注意事项

这里说一下为什么使用可选类型去接JSON的每个属性,以及到底使用let还是var去修饰属性:

  • 使用可选类型,是我个人处于安全考虑,我已经在调试和生产中,见过太多这样的异常,后台传了一个null过来,然后没有使用可选类型去接,App直接崩溃,后台无法保证它传递的每个参数都有值,甚至有些异常情况连后台也无法预测,所以可选类型虽然在后面的编码判断麻烦一点,但是麻烦总比崩溃好。

  • 使用let还是var去修饰属性,取决于在业务中Model中的属性是否会变化,App会不会主动给Model赋新的值,如果没有变化建议使用let,另外使用var可以考虑给Model的属性设置一个初始值,这样就可以不使用可选类型,就算后台传了一个null过来,默认值依旧在。

继续拓展

不知道各位没有注意上面JSON有这么两个字段:

{
    "expires_in":86400,
    "refresh_token_expires_in":2592000
}
复制代码

稍微看一下属性的命名和后面接的数字,就能知道这是时间戳,而在上面的Model中,我们是拿Int?去接的,考虑使用Date?去接一下试试?能顺利返回数据么?

TokenData我们改成这样:

struct TokenData : Codable {

    let accessToken: String?
    let expiresIn: Date?
    let refreshTokenExpiresIn: Date?
    let refreshToken: String?
    
    /// CodingKeys枚举和上面一致就不写了
}
复制代码

截图来看看打印结果:

image.png

从截图上看,修改Int?Date?,还是能成功解析出来。Codable还不错吧。

如果你需要对时间进行更有针对性的解析,就要自定义JSONDecoder类中,DateDecodingStrategy类型的属性了。

对[JSON]的解析

在上篇中,我说了ObjectMapper在解析JSON与[JSON]中需要使用两套Api进行处理,在写BaseModel的时候需要写两个类型进行,但是这一点通过Codable协议解析不在。

import Foundation

struct BaseModel<T: Codable>: Codable {
    let data : T?
    let errorCode : Int?
    let errorMsg : String?
}

struct Item: Codable {
    /// 属性省略
}
复制代码

在使用的时候,BaseModel<Itme>还是BaseModel<[Itme]>,只有接受的JSON数据格式符合要求,都能解析出来,简化了写BaseModel的种类与判断逻辑,一把梭!

这里其实涉及到一个小小的知识点:数组的中的元素遵守Codable协议,那么由这个元素构成的数组也遵守Codable协议。

JSON中的值转为Swift的枚举

通过Codable协议,我们甚至可以直接将JSON的中字符串、数字等转为Swift中的枚举,这样相当于一步到位,减少了中间的逻辑转换,我们看一下下面这段比较中二的代码:


/// 需要注意的是 使用的枚举必须继承, 而且也必须遵守Codable协议, JSON中对应的数据字符串必须和枚举中定义的一模一样,否则就转模型失败 直接整个对象都为空

struct OnPunchMan: Codable {
    var name: String?
    var birthday: Date?
    var sex: Sex?
    var skill: [Skill]?
    var vocation: [Vocation]? // Vocation类型虽然可以在赋值的时候直接附一个数组形式,但是在从JSON转模型的时候, 如果JSON是一个[0, 1, 2]的数组,那么Vocation也必须也是一个数组, 否则也转成空了
}

/// rawValue为String的枚举
///
/// - man: 男
/// - woman: 女
/// - unknown: 未知
enum Sex: String, Codable {
    case man = "man"
    case woman = "woman"
    case unknown = "unknown"
}

/// 必杀技系列
/// 注意 就算没有等号赋值 也是可以编译成功的 case noCare的默认值就是"noCare", case veryCare的默认值就是"veryCare"以此类推
/// - noCare: 琦玉一般系列
/// - veryCare: 琦玉认真系列
/// - haha: 琦玉沙雕系列
enum Skill: String, Codable {
    case noCare
    case veryCare
    case haha
}

/// 职业
struct Vocation: OptionSet, Codable {
    typealias RawValue = Int
    
    var rawValue: Int
    
    init(rawValue: Int) {
        self.rawValue = rawValue
    }
    
    static let saber = Vocation(rawValue: 1 << 0)
    
    static let archer = Vocation(rawValue: 1 << 1)
    
    static let lancer = Vocation(rawValue: 1 << 2)
    
    static let rider = Vocation(rawValue: 1 << 3)
    
    static let berserker = Vocation(rawValue: 1 << 4)
    
    static let caster = Vocation(rawValue: 1 << 5)
    
    static let assassin = Vocation(rawValue: 1 << 6)
}
复制代码

例子如下:

func jsonToModelEnum() {
        let onePunchManString = #"{"name": "sola", "birthday": 562608000, "sex": "unknown", "skill": ["noCare", "veryCare", "haha"], "vocation": [0, 1, 2]}"#
        let onePunchManStringData = onePunchManString.data(using: .utf8)!
        let onePunchMan = try? JSONDecoder().decode(OnPunchMan.self, from: onePunchManStringData)
        print(onePunchMan)
}
复制代码

打印结果:

Optional(OnPunchMan(name: Optional("sola"), birthday: Optional(2018-10-30 16:00:00 +0000), sex: Optional(Sex.unknown), skill: Optional([Skill.noCare, Skill.veryCare,Skill.haha]), vocation: Optional([Vocation(rawValue: 0),Vocation(rawValue: 1),Vocation(rawValue: 2)])))
复制代码

总结

Swift中JSON解析在经历起起伏伏之后,在Codable出现后,基本上趋于大统一时代。

Codable自带官方光环,减少了第三库框架的引入,简单易用,应该算是Swift中JSON解析的第一选择。

至于Codable中源代码,就当是诸君的课后作业吧:

public typealias Codable = Decodable & Encodable

/// A type that can encode itself to an external representation.
public protocol Encodable {

    /// Encodes this value into the given encoder.
    ///
    /// If the value fails to encode anything, `encoder` will encode an empty
    /// keyed container in its place.
    ///
    /// This function throws an error if any values are invalid for the given
    /// encoder's format.
    ///
    /// - Parameter encoder: The encoder to write data to.
    func encode(to encoder: Encoder) throws
}

/// A type that can decode itself from an external representation.
public protocol Decodable {

    /// Creates a new instance by decoding from the given decoder.
    ///
    /// This initializer throws an error if reading from the decoder fails, or
    /// if the data read is corrupted or otherwise invalid.
    ///
    /// - Parameter decoder: The decoder to read data from.
    init(from decoder: Decoder) throws
}
复制代码

其实在JSON转Model中,我们实际上使用的是Decodable协议,而Encodable是用于将对象进行二进制化的,一般写的使用Codable来联合这两个协议,是出于Model对象可能两方面都需要导致。

资源与工具链接:

下面是一些参考文章与JSON转Model的工具。

Swift - Codable 使用小记

JSONExport

quicktype

明日继续

针对一些项目开发的准备工作与基础的介绍基本告一段落,后面着重进行玩安卓App的编写与例子,其实就是堆砌代码,大家加油。

文章分类
iOS
文章标签