Swift Json實(shí)例詳細(xì)解析
前言
客戶端開(kāi)發(fā)項(xiàng)目中,不可避免地需要解析網(wǎng)絡(luò)數(shù)據(jù)---將服務(wù)端下發(fā)的JSON數(shù)據(jù)解析成客戶端可閱讀友好的Model。Objective-C下使用最多的是JSONModel,它能在OC Runtime基礎(chǔ)下很好地完成解析工作。那么在純Swift代碼中,這個(gè)功能是如何實(shí)現(xiàn)的?下面開(kāi)始我們的探索~
- 手動(dòng)解析
- 原生:Swift4.0 JSONDecoder
- JSONDecoder 問(wèn)題 及 解決方案
手動(dòng)解析
假設(shè)一個(gè)User類(lèi)要解析,Json如下:
{
"userId": 1,
"name": "Jack",
"height": 1.7,
}
對(duì)應(yīng)的創(chuàng)建一個(gè)User結(jié)構(gòu)體(也可以是類(lèi)):
struct User {
var userId: Int?
var name: String?
var height: CGFloat?
}
把JSON轉(zhuǎn)成User
在Swift4.0前,我們以手動(dòng)解析的方式將JSON model化。給User加一個(gè)以JSON為參數(shù)的初始化方法,代碼如下:
struct User {
...
init?(json: [String: Any]) {
guard let userId = json["userId"] as? Int,
let name = json["name"] as? String,
let height = json["height"] as? CGFloat else { return nil }
self.userId = userId
self.name = name
self.height = height
}
}
依次從json中取出model所需的具體類(lèi)型的數(shù)據(jù),填充到具體對(duì)應(yīng)屬性中。如果其中一個(gè)轉(zhuǎn)換失敗或者沒(méi)有值,初始化會(huì)失敗返回nil。
如果某個(gè)值不需要強(qiáng)校驗(yàn),直接取值再賦值,把guard let內(nèi)的語(yǔ)句去掉。例如,若height不用校驗(yàn),可看如下代碼:
struct User {
...
init?(json: [String: Any]) {
guard let userId = json["userId"] as? Int,
let name = json["name"] as? String else { return nil }
self.userId = userId
self.name = name
self.height = json["height"] as? CGFloat
}
}
原生:Swift4.0 JSONDecoder
2017年6月份左右Swift4.0發(fā)布,其中一個(gè)重大更新就是JSON的加解密。擺脫手工解析字段的繁瑣,聊聊幾行代碼就可將JSON轉(zhuǎn)換成Model。與Objective-C下的JSONModel極為相似。同樣解析上述例子中的User,Swift4.0可以這么寫(xiě):
struct User: Decodable {
var userId: Int?
var name: String?
var height: CGFloat?
}
let decoder = JSONDecoder()
if let data = jsonString.data(using: String.Encoding.utf8) {
let user = try? decoder.decode(User.self, from: data)
}
so easy~ 與手動(dòng)解析不同點(diǎn)在于:
1.移除了手寫(xiě)init?方法。不需要手動(dòng)解了
2.User實(shí)現(xiàn)Decodable協(xié)議,協(xié)議的定義如下:
/// 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.
public init(from decoder: Decoder) throws
}
Decodable協(xié)議只有一個(gè)方法public init(from decoder: Decoder) throws---以Decoder實(shí)例進(jìn)行初始化,初始化失敗可能拋出異常。慶幸的是,只要繼承Decodable協(xié)議,系統(tǒng)會(huì)自動(dòng)檢測(cè)類(lèi)中的屬性進(jìn)行初始化工作,省去了人工解析的麻煩~
3.使用了JSONDecoder。它是真正的解析工具,主導(dǎo)整個(gè)解析過(guò)程
讀到這里,是不是覺(jué)得人生從黑暗邁向了光明~~
可是,它并不完美...
JSONDecoder問(wèn)題及方案
解析JSON經(jīng)常遇到這樣兩種不一致問(wèn)題:
- 服務(wù)端下發(fā)的key跟端上不一致。比如,服務(wù)端下發(fā)key="order_id",端上定義key="orderId"
- 服務(wù)端下發(fā)的日期表達(dá)是yyyy-MM-dd HH:mm或者時(shí)間戳,但端上是Date類(lèi)型
- 服務(wù)端下發(fā)的基本類(lèi)型和端上定義的不一致。服務(wù)端下發(fā)的是String,端上定義的Int,等
前兩個(gè)問(wèn)題JSONDecoder都能很好地解決。
第一個(gè)key不一致問(wèn)題,JSONDecoder有現(xiàn)成的方案。以上面介紹的例子來(lái)說(shuō),假設(shè)服務(wù)端返回的key是user_id而不是userId,那么我們可以使用JSONDecoder的CodingKeys像JSONModel一樣對(duì)屬性名稱(chēng)在加解密時(shí)的名稱(chēng)做轉(zhuǎn)換。User修改如下:
struct User: Decodable {
var userId: Int?
var name: String?
var height: CGFloat?
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case name
case height
}
}
第二個(gè),Date轉(zhuǎn)換問(wèn)題。JSONDecoder也為我們提供了單獨(dú)的API:
open class JSONDecoder {
/// The strategy to use for decoding `Date` values.
public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
/// Decode the `Date` as a UNIX timestamp from a JSON number.
case secondsSince1970
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
case millisecondsSince1970
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
case iso8601
/// Decode the `Date` as a string parsed by the given formatter.
case formatted(DateFormatter)
/// Decode the `Date` as a custom value decoded by the given closure.
case custom((Decoder) throws -> Date)
}
......
/// The strategy to use in decoding dates. Defaults to `.deferredToDate`.
open var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy
}
設(shè)置好了JSONDecoder屬性dateDecodingStrategy后,解析Date類(lèi)型就會(huì)按照指定的策略進(jìn)行解析。
類(lèi)型不一致
至此,JSONDecoder為我們提供了
- 解析不同key值對(duì)象
- Date類(lèi)型可自定義轉(zhuǎn)換
- Float在一些正負(fù)無(wú)窮及無(wú)值得特殊表示。(出現(xiàn)的概率很少,不作具體說(shuō)明了)
但遇到基本類(lèi)型端上與服務(wù)端不一致時(shí)(比如一個(gè)數(shù)字1,端上的Code是Int型,服務(wù)端下發(fā)String:"1"),JSONDecoder會(huì)拋出typeMismatch異常而終結(jié)整個(gè)數(shù)據(jù)的解析。
這讓人有點(diǎn)懊惱,端上的應(yīng)用,我們希望它能夠盡可能穩(wěn)定,而不是某些情況下遇到若干個(gè)基本類(lèi)型不一致整個(gè)解析就停止,甚至是 Crash。
如下面表格所示,我們希望類(lèi)型不匹配時(shí),能夠這么處理:左列代表前端的類(lèi)型,右列代表服務(wù)端類(lèi)型,每一行代表前端類(lèi)型為X時(shí),能從服務(wù)端下發(fā)的哪些類(lèi)型中轉(zhuǎn)化,比如String 可以從 IntorFloat轉(zhuǎn)化。這幾個(gè)類(lèi)型基本能覆蓋日常服務(wù)端下發(fā)的數(shù)據(jù),其它類(lèi)型的轉(zhuǎn)化可根據(jù)自己的需求擴(kuò)充。
前端 |
服務(wù)端 |
|---|---|
| String | Int,F(xiàn)loat |
| Float | String |
| Double | String |
| Bool | String, Int |
JSONDecoder沒(méi)有給我們便利的這種異常處理的API。如何解決呢?最直接的想法,在具體的model內(nèi)實(shí)現(xiàn)init(decoder: Decoder)手動(dòng)解析可以實(shí)現(xiàn),但每個(gè)都這么處理太麻煩。
解決方案:KeyedDecodingContainer方法覆蓋
研究JSONDecoder的源碼,在解析自定義Model過(guò)程中,會(huì)發(fā)現(xiàn)這樣一個(gè)調(diào)用關(guān)系。
// 入口方法 JSONDecoder decoder(type:Type data:Data) // 內(nèi)部類(lèi),真實(shí)用來(lái)解析的 _JSONDecoder unbox(value:Any type:Type) // Model調(diào)用init方法 Decodable init(decoder: Decoder) // 自動(dòng)生成的init方法調(diào)用container Decoder container(keyedBy:CodingKeys) // 解析的容器 KeyedDecodingContainer decoderIfPresent(type:Type) or decode(type:Type) // 內(nèi)部類(lèi),循環(huán)調(diào)用unbox _JSONDecoder unbox(value:Any type:Type) ...循環(huán),直到基本類(lèi)型
最終的解析落到,_JSONDecoder的unbox 及 KeyedDecodingContainer的decoderIfPresent decode方法。但_JSONDecoder是內(nèi)部類(lèi),我們處理不了。最終決定對(duì)KeyedDecodingContainer下手,其中部分代碼如下:
extension KeyedDecodingContainer {
.......
/// Decode (Int, String) -> Int if possiable
public func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int? {
if let value = try? decode(type, forKey: key) {
return value
}
if let value = try? decode(String.self, forKey: key) {
return Int(value)
}
return nil
}
.......
/// Avoid the failure just when decoding type of Dictionary, Array, SubModel failed
public func decodeIfPresent<T>(_ type: T.Type, forKey key: K) throws -> T? where T : Decodable {
return try? decode(type, forKey: key)
}
}
上述代碼中,第一個(gè)函數(shù)decodeIfPresent(_ type: Int.Type, forKey key: K)是以key的信息解析出Int?值。這里覆蓋了KeyedDecodingContainer中的該函數(shù)的實(shí)現(xiàn),現(xiàn)在已try?的形式以Int類(lèi)型解析,解析成功則直接返回,失敗則以String類(lèi)型解析出一個(gè)StringValue,如果解析成功,再把String轉(zhuǎn)換成Int?值。
為什么要寫(xiě)第二個(gè)函數(shù)呢?
場(chǎng)景:當(dāng)我們Model內(nèi)有其他的非基本類(lèi)型的Model,比如其他自定義Model,Dictionary<String, Any>,Array<String>等,當(dāng)這些Model 類(lèi)型不匹配或者出錯(cuò)誤時(shí)也會(huì)拋出異常,導(dǎo)致整個(gè)大Model解析失敗。
覆蓋decodeIfPresent<T>(_ type: T.Type, forKey key: K)可以避免這些場(chǎng)景。至此,當(dāng)類(lèi)型過(guò)程中出現(xiàn)解析的Optional類(lèi)型出現(xiàn)不匹配時(shí),我們要不是通過(guò)轉(zhuǎn)換,要不就是給其賦值nil,避免了系統(tǒng)此時(shí)直接throw exception導(dǎo)致退出整個(gè)解析過(guò)程的尷尬。
為何不覆蓋decode方法?decodeIfPresent可以返回Optional值,decode返回確定類(lèi)型值??紤]到如果Model內(nèi)如果定義的類(lèi)型是No-Optional型,那么可以認(rèn)為開(kāi)發(fā)者確定該值必須存在,如果不存在Model很可能是錯(cuò)誤的,所以直接fail。
完整擴(kuò)展代碼點(diǎn)我 (本地下載點(diǎn)我)
總結(jié)
Swift4.0 JSONDecoder確實(shí)為解析數(shù)據(jù)帶來(lái)了極大的便利。使用方式上類(lèi)似Objective-C下的JSONModel。但實(shí)際開(kāi)發(fā)中還是需要一些改造才能更好地服務(wù)于我們。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)我們的支持。
上一篇:Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添加私密保護(hù)功能
欄 目:Swift
本文標(biāo)題:Swift Json實(shí)例詳細(xì)解析
本文地址:http://www.jygsgssxh.com/a1/Swift/11959.html
您可能感興趣的文章
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift利用Decodable解析JSON的一個(gè)小問(wèn)題詳解
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添加私密保護(hù)功能
- 01-11Swift 4.0中如何引用3.0的第三方庫(kù)


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-11Swift利用Decodable解析JSON的一個(gè)小問(wèn)題
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift Json實(shí)例詳細(xì)解析
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添
隨機(jī)閱讀
- 01-10delphi制作wav文件的方法
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 04-02jquery與jsp,用jquery
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什


