<iOS 實踐>利用 Codable 協(xié)議實現(xiàn) JSON 編解碼

Foundation 中提供的 JSONSerialization 類也可以實現(xiàn) JSON 和Swift 類型的雙向轉(zhuǎn)換, 但提供的功能有限, 如果只是簡單的需求, 也可以使用它: JSONSerialization

下面介紹如何利用 Codable 協(xié)議實現(xiàn) JSON 的編解碼.

參考鏈接:

  1. 官方文檔.

1 簡介

Swift 標準庫中有兩個協(xié)議 EncodableDecodable, 作用分別是:

  • Encodable: Encodable 的實現(xiàn)類型可以進行從 Swift 類型到 JSON 等外部表示的轉(zhuǎn)換.
  • Decodable: Decodable 的實現(xiàn)類型可以進行從 JSON 等外部表示到 Swift 類型的轉(zhuǎn)換.

在開發(fā)中, 通過 Encoder(編碼器) 和 Decoder(解碼器) 的實現(xiàn)類來進行編解碼操作, 實現(xiàn)類包括 JSONEncoderJSONDecoder, 另外還有針對 plist 進行編碼的 PropertyListEncoder 等.

Codable 協(xié)議是 Encodable 和 Decodable 協(xié)議的組合, 如果實現(xiàn)了 Codable, 就表明實現(xiàn)了 Encodable 和 Decodable. 如果一個類型需要實現(xiàn)和外部表示的雙向轉(zhuǎn)換, 就需要實現(xiàn) Codable 協(xié)議.

2 自動實現(xiàn) Encodable 和 Decodable

**如果某類型的所有屬性都是 Codable 的, 則其就自動實現(xiàn) Codable **.

Swift 標準庫類型都實現(xiàn)了 Codable, 比如 String, Double, Int 等, Foundation 庫中也有許多類型實現(xiàn)了 Codable, 比如 Date, Data, 以及 URL.

來看下面的代碼:

struct Landmark {
    var name: String
    var foundingYear: Int
}

等價于如下代碼:

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
}

多個 Codable 的組合也是 Codable :

struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
}

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
}

其他的一些內(nèi)置類型, 比如 Array, Dictionary 以及 Optional 的也已實現(xiàn) Codable 協(xié)議:

struct Landmark: Codable {
    var name: String
    var foundingYear: Int
    var location: Coordinate
    // 如下是一些內(nèi)置類型    
    var vantagePoints: [Coordinate]
    var metadata: [String: String]
    var website: URL?
}

3 單獨實現(xiàn) EncodableDecodable

如果一個類型只需要進行單獨的編碼或解碼操作, 則可以只實現(xiàn) EncodableDecodable:

如果某類型只需要進行編碼操作:

struct Landmark: Encodable {// 遵守 Encodable 協(xié)議
    var name: String
    var foundingYear: Int
}

又如某個類型只會進行 JSON 解碼操作, 則它可以只實現(xiàn) Decodable:

struct Landmark: Decodable {// 遵守 Decodable 協(xié)議
    var name: String
    var foundingYear: Int
}

4 JSON 編解碼操作實例

有了上面的鋪墊, 就可以看看在實際編程中如何進行操作了, 如下例子中都使用 JSON作為外部類型.

4.1 Codable 協(xié)議實現(xiàn)類的編碼操作

定義 Coordinate 類型:

struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
}

下面的代碼將 Coordinate 編碼為 JSON:

let coordinate = Coordinate(latitude: 11.2, longitude: 12.3)

let encoder = JSONEncoder()
encoder.outputFormatting = .sortedKeys

let data = try encoder.encode(pear)

print(String(data: data, encoding: .utf8)!) 
// 輸出為 {"latitude":11.199999999999999,"longitude":12.300000000000001}

從 JSON 中解碼 Coordinate 對象:

let jsonstr = """
{"latitude":11.199999999999999,"longitude":12.300000000000001}
"""
let coordinateData = jsonstr.data(using: .utf8)!

let decoder = JSONDecoder()
let coordinate = try decoder.decode(Coordinate.self, from: jsonstr)

print(coordinate.latitude)      //11.2
print(coordinate.longitude) //12.3

4.2 自定義 JSON Key

在實際開發(fā)中, 經(jīng)常希望在編解碼 JSON 時指定實體屬性名對應(yīng)的 JSON key, 這時可以在實體類型中聲明一個 enum, 命名為 CodingKeys, 這個 enum 遵守 CodingKey 協(xié)議:

struct Coordinate: Codable {
    var latitude: Double
    var longitude: Double
    
    enum CodingKeys: String, CodingKey {// 在這里指定屬性和 JSON key 的對應(yīng)關(guān)系
        case latitude = "latitude_value"
        // 如果屬性名和 JSON key 一致, 則直接添加進來即可, 如下所示:
        case longitude
    }
}

4.3 忽略屬性

如果要在編碼或解碼時忽略某個屬性, 可以像下面這樣操作.

比如要忽略 longitude 屬性, 可以直接在上述代碼中的 CodingKeys 枚舉中將其移除:

struct Coordinate: Codable {
    var latitude: Double
    // 被忽略的屬性如果不是可選的, 則必須具有默認值, 否則整個實體都無法被解析出來
    var longitude: Double = 0

    enum CodingKeys: String, CodingKey {// 在這里指定屬性和 JSON key 的對應(yīng)關(guān)系
        case latitude = "latitude_value"
        // 要忽略某個屬性, 則不將該屬性添加到這個 enum 中
        // case longitude
    }
}

要注意, 如果該類型需要被解碼, 則被忽略的屬性必須具有默認值, 否則整個實體都無法被解碼出來.

比如進行編碼操作, 上述實體對象的輸出為:

// {"latitude_value":11.199999999999999}

4.4 手動實現(xiàn)編解碼

若自動編解碼的默認規(guī)則無法滿足要求, 還可以手動設(shè)置編解碼規(guī)則, 以獲得更大的靈活性.

重新定義 Coordinate 類型如下:

struct Coordinate {
    var latitude: Double
    var longitude: Double
    var elevation: Double

    enum CodingKeys: String, CodingKey {
        case latitude = "latitude_value"
        case longitude = "longitude_value"
        case additionalInfo
    }
    
    enum AdditionalInfoKeys: String, CodingKey {
        case elevation
    }
}

假設(shè) JSON 數(shù)據(jù)結(jié)構(gòu)和這個實體結(jié)構(gòu)稍有不同, 就永遠無法將該實體解析出來的. 這個時候就需要手動設(shè)置編解碼規(guī)則了.

要手動對這個類型設(shè)置編解碼規(guī)則, 需要將編解碼協(xié)議放到擴展中去實現(xiàn):

extension Coordinate: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        latitude = try values.decode(Double.self, forKey: .latitude)
        longitude = try values.decode(Double.self, forKey: .longitude)

        let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
    }
}

extension Coordinate: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)

        var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
        try additionalInfo.encode(elevation, forKey: .elevation)
    }
}

5 一些實踐做法

在實踐中, 常將 Decodable 類型中的所有屬性都定義為 optional, 這樣服務(wù)端的 JSON 細微改變不會影響到整個實體的解析.

如下定義了一個實體:

struct DataRootClass: Decodable {
    var status: String
    var message: String
    var code: Int
}

如果當前響應(yīng) JSON 中的 message 字段變成了 msg, 則整個實體都無法解析出來, 這樣的情況在實際開發(fā)中很常見( - _ -||), 一個解決方案就是把屬性都變?yōu)榭蛇x, 這樣盡最大可能不影響到實際解析結(jié)果.

struct DataRootClass: Decodable {
    var status: String?
    var message: String?
    var code: Int?
}

另外針對一些復(fù)雜的 JSON 結(jié)構(gòu), 更好的辦法是先使用 SwiftyJSON 將內(nèi)部數(shù)據(jù)剝離開, 然后單獨進行解析, 這樣避免寫復(fù)雜的自定義解析規(guī)則代碼.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碌上,一起剝皮案震驚了整個濱河市掀鹅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌次屠,老刑警劉巖园匹,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異劫灶,居然都是意外死亡裸违,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門本昏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來供汛,“玉大人,你說我怎么就攤上這事涌穆≌颍” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵蒲犬,是天一觀的道長朱监。 經(jīng)常有香客問我岸啡,道長原叮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮奋隶,結(jié)果婚禮上擂送,老公的妹妹穿的比我還像新娘。我一直安慰自己唯欣,他們只是感情好嘹吨,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著境氢,像睡著了一般蟀拷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萍聊,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天问芬,我揣著相機與錄音,去河邊找鬼寿桨。 笑死此衅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的亭螟。 我是一名探鬼主播挡鞍,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼预烙!你這毒婦竟也來了墨微?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤扁掸,失蹤者是張志新(化名)和其女友劉穎欢嘿,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體也糊,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡炼蹦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了狸剃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掐隐。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钞馁,靈堂內(nèi)的尸體忽然破棺而出虑省,到底是詐尸還是另有隱情,我是刑警寧澤僧凰,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布探颈,位于F島的核電站,受9級特大地震影響训措,放射性物質(zhì)發(fā)生泄漏伪节。R本人自食惡果不足惜光羞,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怀大。 院中可真熱鬧纱兑,春花似錦、人聲如沸化借。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蓖康。三九已至铐炫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒜焊,已是汗流浹背驳遵。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留山涡,地道東北人堤结。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像鸭丛,于是被迫代替她去往敵國和親竞穷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫鳞溉、插件瘾带、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評論 4 62
  • 周一,iPhone SE發(fā)布了熟菲。一時間看政,各種聲音沖刷著社交網(wǎng)絡(luò)〕保看著那些對蘋果或冷嘲熱諷或喜愛有加的評論允蚣,我想是時...
    字芽閱讀 3,883評論 41 36
  • 文|金玲 你希望別人用什么方式表揚你呢? 那天群主發(fā)出這個提問時呆贿,覺得很有意思嚷兔,回想一下過往的被表揚,覺得印象深刻...
    金玲老師的生涯空間站閱讀 7,947評論 7 13
  • 小張最近很苦惱壶运。他搞不明白,為什么他有研究生學歷卻找不到一份好工作浪秘。當初考研的時候蒋情,他是抱著有了高學歷就能找到好工...
    立雪寒梅1閱讀 393評論 3 4
  • 最近微信小程序很火埠况,說起這個,不得不提到蘋果公司剛剛推出的IMessage app恕出,個人覺得微信小程序算是跟蘋果這...
    風御軒閱讀 171評論 0 0