JSONEncoder / JSONDecoder
一個(gè)類型通過聲明自己遵守 Encodable 和/或 Decodable 協(xié)議匿辩,來表明可以被序列化和/或反序列化。這兩個(gè)協(xié)議都只約束了一個(gè)方法套硼,其中:Encodable 約束了 encode(to:),它定義了一個(gè)類型如何對自身進(jìn)行編碼;而 Decodable 則約束了一個(gè)初始化方法允华,用來從序列化的數(shù)據(jù)中創(chuàng)建實(shí)例:
/// 一個(gè)類型可以將自身編碼為某種外部表示形式井联。
public protocol Encodable {
/// 將值編碼到給定的 encoder 中卜壕。
public func encode(to encoder: Encoder) throws
}
/// 一個(gè)類型可以從某種外部表示形式中解碼得到自身。
public protocol Decodable {
/// 從給定的 decoder 中解碼來創(chuàng)建新的實(shí)例烙常。
public init(from decoder: Decoder) throws
}
Encoding / Decoding
struct SPUserModel: Codable {
var name: String
var contact: SPContactModel?
}
struct SPContactModel: Codable {
var mobileTelephone = ""
var fixedTelephone = ""
}
Encoding
let models = [SPUserModel(name: "zhangsan", contact: SPContactModel(mobileTelephone: "138xxxxxxxx", fixedTelephone: "010-xxxxxxx")),
SPUserModel(name: "lisi", contact: SPContactModel(mobileTelephone: "135xxxxxxxx", fixedTelephone: "020-xxxxxxx"))]
do {
let encoder = JSONEncoder()
let jsonData = try encoder.encode(models)
let jsonString = String(decoding: jsonData, as: UTF8.self)
dump(jsonString)
} catch { }
Decoding
do {
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData)
dump(decoded)
} catch { }
合成的代碼
Coding Keys
在 SPUserModel
里轴捎,編譯器會生成一個(gè)叫做 CodingKeys 的私有枚舉類型鹤盒。這個(gè)枚舉包含的成員與結(jié)構(gòu)體中的存儲屬性一一對應(yīng)。
private enum CodingKeys: String, CodingKey {
case name
case contact
}
encode(to:) 方法
下面是編譯器為 SPUserModel
結(jié)構(gòu)體生成的 encode(to:) 方法:
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(contact, forKey: .contact)
}
init(from:) 初始化方法
當(dāng)我們調(diào)用 try decoder.decode([SPUserModel].self, from: jsonData)
時(shí)侦副,解碼器會按照我們傳入的類型 (這里是 [SPUserModel])侦锯,使用 Decodable 中定義的初始化方法創(chuàng)建一個(gè)該類型的實(shí)例。
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
contact = try container.decode(SPContactModel.self, forKey: .contact)
}
手動遵守協(xié)議
自定義 Coding Keys
我們可以創(chuàng)建自定義的 CodingKeys 枚舉秦驯,在這個(gè)枚舉中尺碰,我們可以:
- 在編碼后的輸出中,用明確指定的字符串值重命名字段译隘。
- 將某個(gè)鍵從枚舉中移除亲桥,以此跳過與之對應(yīng)字段。
想要設(shè)置一個(gè)不同的名字固耘,我們需要明確將枚舉的底層類型設(shè)置為 String题篷。例如, API
數(shù)據(jù)某個(gè)字段 name
更改為與模型不匹配的字段 username
厅目,則需要自定義編碼鍵番枚,添加以下代碼,枚舉 CodingKeys
中包含 SPUserModel
模型中所有的屬性璧瞬,如此則可以正常解碼户辫。
如果枚舉里沒有包含 name
鍵,因此編碼時(shí) name
將會被跳過嗤锉,只有 contact
會被編碼渔欢,被跳過的屬性必須賦個(gè)默認(rèn)值,不然將會編譯失敗瘟忱。
let json = """
[{
"username": "zhangsan",
"contact": {"mobileTelephone": "138xxxxxxxx",
"fixedTelephone": "010-xxxxxxx"
}
},
{
"username": "lisi",
"contact": {"mobileTelephone": "138xxxxxxxx",
"fixedTelephone": "010-xxxxxxx"
}
}]
"""
struct SPUserModel: Codable {
var name = ""
var contact: SPContactModel?
private enum CodingKeys: String, CodingKey {
case name = "username"
case contact
}
}
struct SPContactModel: Codable {
var mobileTelephone = ""
var fixedTelephone = ""
}
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
dump(decoded)
} catch { }
自定義的 encode(to:) 和 init(from:) 實(shí)現(xiàn)
JSONEncoder
和 JSONDecoder
默認(rèn)就可以處理可選值奥额。當(dāng)目標(biāo)類型中的一個(gè)屬性是可選值,如果數(shù)據(jù)中對應(yīng)的值不存在的話访诱,解碼器將會正確地跳過這個(gè)屬性垫挨。如下面的 contact
屬性
let json = """
[{
"name": "zhangsan"
},
{
"name": "lisi"
}]
"""
struct SPUserModel: Codable {
var name = ""
var contact: SPContactModel?
}
struct SPContactModel: Codable {
var mobileTelephone = ""
var fixedTelephone = ""
}
如果數(shù)據(jù)和所期待的形式不同,則解碼錯(cuò)誤触菜。 比如給 contact
對象一個(gè)空json對象
let json = """
[{
"name": "zhangsan",
"contact": { }
},
{
"name": "lisi",
"contact": { }
}]
"""
error: The data couldn’t be read because it is missing.
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
} catch {
//The data couldn’t be read because it is missing.
print(error.localizedDescription)
}
重載 Decodable 的初始化方法 init(from:)
九榔,明確地捕獲我們所期待的錯(cuò)誤,解碼器就可以成功地解碼這個(gè)錯(cuò)誤的 JSON 了
struct SPUserModel: Codable {
var name = ""
var contact: SPContactModel?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
do {
self.contact = try container.decodeIfPresent(SPContactModel.self, forKey: .contact)
} catch DecodingError.keyNotFound {
self.contact = nil
}
}
}
struct SPContactModel: Codable {
var mobileTelephone = ""
var fixedTelephone = ""
}
常見的編碼任務(wù)
讓其他人的代碼滿足 Codable
假如 SPUserModel
中存在并不滿足 Codable 協(xié)議的類涡相,比如 CLLocationCoordinate2D
哲泊,編譯器現(xiàn)在會 (正確地) 抱怨說它無法為 SPUserModel
自動生成實(shí)現(xiàn) Codable 的代碼,因?yàn)樗? coordinate
屬性不再是遵從 Codable 的類型了催蝗。
struct SPUserModel: Codable {
var name = ""
var coordinate: CLLocationCoordinate2D
}
解決辦法1
struct SPUserModel: Codable {
var name = ""
var coordinate: CLLocationCoordinate2D
private enum CodingKeys: String, CodingKey {
case name
case latitude = "lat"
case longitude = "lon"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
// 分別編碼緯度和經(jīng)度
try container.encode(coordinate.latitude, forKey: .latitude)
try container.encode(coordinate.longitude, forKey: .longitude)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
// 從緯度和經(jīng)度重新構(gòu)建 CLLocationCoordinate2D
self.coordinate = CLLocationCoordinate2D (
latitude: try container.decode(Double.self, forKey: .latitude),
longitude: try container.decode(Double.self, forKey: .longitude)
)
}
}
let json = """
[{
"name": "zhangsan",
"lat": 312312313,
"lon": 3452423424
},
{
"name": "lisi",
"lat": 123132343,
"lon": 3453432423
}]
"""
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
//Optional(__C.CLLocationCoordinate2D(latitude: 312312313.0, longitude: 3452423424.0))
dump(decoded)
} catch { }
解決辦法2:嵌套容器
struct SPUserModel: Codable {
var name = ""
var coordinate: CLLocationCoordinate2D
private enum CodingKeys: String, CodingKey {
case name
case coordinate
}
// 嵌套容器的編碼鍵
private enum CoordinateCodingKeys: CodingKey {
case latitude
case longitude
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
var coordinateContainer = container.nestedContainer(keyedBy: CoordinateCodingKeys.self, forKey: .coordinate)
try coordinateContainer.encode(coordinate.latitude, forKey: .latitude)
try coordinateContainer.encode(coordinate.longitude, forKey: .longitude)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
let coordinateContainer = try container.nestedContainer(keyedBy: CoordinateCodingKeys.self, forKey: .coordinate)
self.coordinate = CLLocationCoordinate2D (
latitude: try coordinateContainer.decode(Double.self, forKey: .latitude),
longitude: try coordinateContainer.decode(Double.self, forKey: .longitude)
)
}
}
let json = """
[{
"name": "zhangsan",
"coordinate": {
"latitude": 279886268,
"longitude": 123678613
}
},
{
"name": "lisi",
"coordinate": {
"latitude": 221311,
"longitude": 67868
}
}]
"""
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
//Optional(__C.CLLocationCoordinate2D(latitude: 279886268.0, longitude: 123678613.0))
dump(decoded)
} catch { }
解決辦法3
struct SPCoordinate: Codable {
var latitude: Double
var longitude: Double
}
struct SPUserModel: Codable {
var name: String
private var _coordinate: SPCoordinate
var coordinate: CLLocationCoordinate2D {
get {
return CLLocationCoordinate2D(latitude: _coordinate.latitude,
longitude: _coordinate.longitude)
}
set {
_coordinate = SPCoordinate(latitude: newValue.latitude,
longitude: newValue.longitude)
}
}
private enum CodingKeys: String, CodingKey {
case name
case _coordinate = "coordinate"
}
}
let json = """
[{
"name": "zhangsan",
"coordinate": {
"latitude": 279886268,
"longitude": 123678613
}
},
{
"name": "lisi",
"coordinate": {
"latitude": 221311,
"longitude": 67868
}
}]
"""
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
//Optional(__C.CLLocationCoordinate2D(latitude: 279886268.0, longitude: 123678613.0))
dump(decoded)
} catch { }
tips
1. 屬性樣式轉(zhuǎn)換(mobileTelephone -> mobile_telephone)
假如 API
數(shù)據(jù)某個(gè)字段 mobileTelephone
更改為 mobile_telephone
樣式切威,則會出現(xiàn)錯(cuò)誤 :
let json = """
[{
"name": "zhangsan",
"contact": {"mobile_telephone": "138xxxxxxxx",
"fixed_telephone": "010-xxxxxxx"
}
},
{
"name": "lisi",
"contact": {"mobile_telephone": "138xxxxxxxx",
"fixed_telephone": "010-xxxxxxx"
}
}]
"""
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
print(decoded.first?.contact?.fixedTelephone)
} catch {
//error: The data couldn’t be read because it is missing.
print(error.localizedDescription)
}
struct SPUserModel: Codable {
var name: String
var contact: SPContactModel?
}
struct SPContactModel: Codable {
var mobileTelephone = ""
var fixedTelephone = ""
}
在需要編碼和解碼的地方添加以下代碼
encoder.keyEncodingStrategy = .convertToSnakeCase
decoder.keyDecodingStrategy = .convertFromSnakeCase
2.嵌套類型改變
假如 API
數(shù)據(jù)嵌套類型更改,而我們?nèi)匀幌M褂们短住?/p>
let json = """
[{
"name": "zhangsan",
"mobile_telephone": "138xxxxxxxx",
"fixed_telephone": "010-xxxxxxx"
},
{
"name": "lisi",
"mobile_telephone": "138xxxxxxxx",
"fixed_telephone": "010-xxxxxxx"
}]
"""
do {
let jsonData = json.data(using: .utf8)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoded = try decoder.decode([SPUserModel].self, from: jsonData!)
} catch { }
struct SPUserModel: Codable {
var name: String
var contact: SPContactModel?
private enum CodingKeys: CodingKey {
case name
case mobileTelephone
case fixedTelephone
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(contact?.mobileTelephone, forKey: .mobileTelephone)
try container.encode(contact?.fixedTelephone, forKey: .fixedTelephone)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.contact = SPContactModel (
mobileTelephone: try container.decode(String.self, forKey: .mobileTelephone),
fixedTelephone: try container.decode(String.self, forKey: .fixedTelephone)
)
}
}
struct SPContactModel: Codable {
var mobileTelephone = ""
var fixedTelephone = ""
}
3.日期的編解碼
encoder.dateEncodingStrategy = .formatted(<#T##DateFormatter#>)
decoder.dateDecodingStrategy = .formatted(<#T##DateFormatter#>)
假如我們希望日期的格式為 "yyyy-MM-dd"
extension DateFormatter {
static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
}
struct SPUserModel: Codable {
var name: String
var birthday: Date?
}
需要設(shè)置解編碼的 dateEncodingStrategy
屬性
let models = [SPUserModel(name: "zhangsan", birthday: Date()),
SPUserModel(name: "lisi", birthday: Date())]
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .formatted(.dateFormatter)
let jsonData = try encoder.encode(models)
let jsonString = String(decoding: jsonData, as: UTF8.self)
} catch { }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(.dateFormatter)
let decoded = try decoder.decode([SPUserModel].self, from: self.jsonData)
} catch { }
PropertyListEncoder / PropertyListDecoder
class SPUserModel: NSObject, Codable {
var name: String
var address: String
init(name: String, address: String) {
self.name = name
self.address = address
}
}
Encoding
let models = [SPUserModel(name: "zhangsan", address: "beijing"),
SPUserModel(name: "lisi", address: "shanghai")]
do {
let data = try PropertyListEncoder().encode(models)
let data2 = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true)
UserDefaults.standard.set(data2, forKey: "key")
} catch { }
Decoding
guard let data = UserDefaults.standard.object(forKey: "key") else { return }
do {
let data2 = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [SPUserModel.self], from: data as! Data)
let model = try PropertyListDecoder().decode([SPUserModel].self, from: data2 as! Data)
dump(model)
} catch { }
--- ---
Encoding
let models = [SPUserModel(name: "zhangsan5", address: "beijing"),
SPUserModel(name: "lisi3", address: "shanghai")]
do {
let data = try PropertyListEncoder().encode(models)
NSKeyedArchiver.archiveRootObject(data, toFile: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/info")
} catch { }
Decoding
guard let data = NSKeyedUnarchiver.unarchiveObject(withFile: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first! + "/info") else { return }
do {
let model = try PropertyListDecoder().decode([SPUserModel].self, from: data as! Data)
} catch { }