这是我参与更文挑战的第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枚举和上面一致就不写了
}
复制代码
截图来看看打印结果:
从截图上看,修改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的工具。
明日继续
针对一些项目开发的准备工作与基础的介绍基本告一段落,后面着重进行玩安卓App的编写与例子,其实就是堆砌代码,大家加油。