Foundation 中提供的 JSONSerialization 類也可以實現(xiàn) JSON 和Swift 類型的雙向轉(zhuǎn)換, 但提供的功能有限, 如果只是簡單的需求, 也可以使用它: JSONSerialization
下面介紹如何利用 Codable 協(xié)議實現(xiàn) JSON 的編解碼.
參考鏈接:
- 官方文檔.
1 簡介
Swift 標準庫中有兩個協(xié)議 Encodable
和 Decodable
, 作用分別是:
- Encodable:
Encodable
的實現(xiàn)類型可以進行從 Swift 類型到 JSON 等外部表示的轉(zhuǎn)換. - Decodable:
Decodable
的實現(xiàn)類型可以進行從 JSON 等外部表示到 Swift 類型的轉(zhuǎn)換.
在開發(fā)中, 通過 Encoder
(編碼器) 和 Decoder
(解碼器) 的實現(xiàn)類來進行編解碼操作, 實現(xiàn)類包括 JSONEncoder 和 JSONDecoder, 另外還有針對 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) Encodable
或 Decodable
如果一個類型只需要進行單獨的編碼或解碼操作, 則可以只實現(xiàn) Encodable
或 Decodable
:
如果某類型只需要進行編碼操作:
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ī)則代碼.