本文的主要內(nèi)容來自于https://benscheirman.com/2017/06/swift-json.html溶推,并在此基礎(chǔ)上進(jìn)行了翻譯和精簡(jiǎn)崎溃。更多細(xì)節(jié)可以直接閱讀原文。
本文中的Swift版本為5.8
快速開始
有些朋友不喜歡長(zhǎng)篇大論的理論和原理講解,想快速掌握大概的使用方法歪泳,所以我在這里舉了兩個(gè)例子,以滿足這方面的需求脾歇。
/*
編碼JSON字符串
*/
struct Person: Codable {
var name: String
var age: Int32
var height: Double
var email: String
}
let person = Person(name: "張三", age: 23, height: 1.78,email: "12345@qq.com")
// 創(chuàng)建json編碼器
let encoder = JSONEncoder()
// 友好輸出蚣录,即包含換行和縮進(jìn)
encoder.outputFormatting = .prettyPrinted
do {
// 編碼
let jsonData = try encoder.encode(person)
let jsonString = String(data: jsonData, encoding: .utf8)
if jsonString != nil {
print(jsonString!)
}
} catch {
print(error.localizedDescription)
}
/*
解碼JSON字符串
*/
struct Person: Codable {
var name: String
var age: Int32
var height: Double
var email: String
}
let jsonString = """
{"age":23,"email":"12345@qq.com","name":"張三","height":1.78}
"""
let decoder = JSONDecoder()
// 創(chuàng)建json解碼器
let jsonData = jsonString.data(using: .utf8)
do {
// 解碼
let person = try decoder.decode(Person.self, from: jsonData!)
print(person)
} catch {
print(error.localizedDescription)
}
如果上面的例子不能解決你的問題,那么我建議全面系統(tǒng)地學(xué)習(xí)json字符串的轉(zhuǎn)換立宜,正所謂磨刀不負(fù)砍柴工冒萄。
簡(jiǎn)單例子
這次在結(jié)構(gòu)體中加入枚舉類型,然后將其編碼成json字符串橙数。
enum Hobby: String, Codable {
case painting
case reading
case swimming
}
// 如果支持編碼和解碼尊流,則必須遵循Codable協(xié)議
struct Student: Codable {
var name: String
var email: String
var hobby: Hobby
}
let stu1 = Student(name: "張三", email: "zhangsan@qq.com", hobby: .painting)
let encoder = JSONEncoder()
do {
// 因?yàn)榫幗獯a可能會(huì)失敗,所以必須要使用try灯帮,并處理發(fā)生失敗的情況
let dataString = try encoder.encode(stu1)
let jsonString = String(data: dataString, encoding: .utf8)
print(jsonString!)
} catch {
print(error.localizedDescription)
}
上面是將struct編碼成json字符串的過程崖技,解碼json字符串到struct的流程與上面類似逻住,可嘗試自己編寫。
自定義字段名
有時(shí)候我們并不想把變量名稱或常量名稱當(dāng)作json字符串中的字段名迎献,那么可以重新定義CodingKeys
枚舉瞎访,達(dá)到自定義json字段名的效果。例子如下忿晕。
struct Student: Codable {
var name: String
var email: String
// 需要遵循CodingKey協(xié)議
enum CodingKeys: String, CodingKey {
case name = "Name"
case email = "Email"
}
}
let stu = Student(name: "張三", email: "zhangsan@qq.com")
let encoder = JSONEncoder()
do {
let jsonData = try encoder.encode(stu)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)
} catch {
print(error.localizedDescription)
}
處理日期
JSON中并沒有對(duì)應(yīng)的日期類型装诡,如果直接編碼的話,會(huì)輸出一串?dāng)?shù)字践盼,而不是相應(yīng)的日期格式鸦采,所以在編碼之前,可以設(shè)置編碼器中的日期格式咕幻。
struct Foo: Codable {
var date: Date
}
let foo = Foo(date: Date.now)
let encoder = JSONEncoder()
// 設(shè)置日期格式
encoder.dateEncodingStrategy = .iso8601
let jsonData = try! encoder.encode(foo)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)
當(dāng)然渔伯,如果上面不滿足需求,那么可以進(jìn)一步自定義肄程。
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
encoder.dateEncodingStrategy = .formatted(df)
如果還是不能滿足需求锣吼,那么還可以使用.custom
,具體使用方法請(qǐng)查閱相關(guān)文檔蓝厌。
處理URL
假設(shè)有如下json字符串玄叠。
{
"title": "百度一下",
"url": "http://baidu.com"
}
那么對(duì)應(yīng)的struct
為如下。
struct Webpage: Codable {
var title: String
var url: URL
}
解碼過程也是相當(dāng)?shù)暮?jiǎn)單拓提,如下所示读恃。
let jsonString = """
{
"title": "百度一下",
"url": "http://baidu.com"
}
"""
let jsonData = jsonString.data(using: .utf8)
let decoder = JSONDecoder()
var page = try! decoder.decode(Webpage.self, from: jsonData!)
處理頂層數(shù)組
假設(shè)有如下json字符串。
[{"name":"張三"},{"name":"李四"},{"name":"王五"}]
解碼過程如下代态。
struct Article: Codable {
var title: String
}
let articlesString = """
[{"title": "標(biāo)題1"}, {"title":"標(biāo)題2"}, {"title":"標(biāo)題3"}]
"""
let data = articlesString.data(using: .utf8)
let decoder = JSONDecoder()
// 注意這里的類型寫法寺惫,數(shù)組中的元素需要遵循Codable協(xié)議
var articles = try! decoder.decode([Article].self, from: data!)
for article in articles {
print(article.title)
}
更復(fù)雜的結(jié)構(gòu)
有些復(fù)雜的json字符串可以由簡(jiǎn)單的結(jié)構(gòu)組成,所以在定義這些json結(jié)構(gòu)時(shí)蹦疑,可以使用嵌套完成復(fù)雜json的結(jié)構(gòu)體的定義西雀。如下面的例子。
{
"meta": {
"page": 1,
"total_pages": 4,
"per_page": 10,
"total_records": 38
},
"breweries": [
{
"id": 1234,
"name": "Saint Arnold"
},
{
"id": 52892,
"name": "Buffalo Bayou"
}
]
}
struct PagedBreweries : Codable {
struct Meta : Codable {
let page: Int
let totalPages: Int
let perPage: Int
let totalRecords: Int
enum CodingKeys : String, CodingKey {
case page
case totalPages = "total_pages"
case perPage = "per_page"
case totalRecords = "total_records"
}
}
struct Brewery : Codable {
let id: Int
let name: String
}
let meta: Meta
let breweries: [Brewery]
}
自定義編解碼
上面例子中的編解碼過程都不需要我們干預(yù)歉摧,非常方便我們使用艇肴,但如果想更精準(zhǔn)的控制編解碼過程,那么就需要自定義編解碼過程叁温。
編碼
這里以車為例子豆挽,里面包含一些基本信息,有些信息沒有實(shí)際意義券盅,僅僅為了演示需要帮哈。
enum CarColor: String, Codable {
case white
case black
case red
case blue
}
struct Car: Codable {
var owner: String
var manufacturer: String
var price: String
var color: CarColor
// 無任何意義,僅用于演示
var sizes: [Double]
}
然后對(duì)車進(jìn)行擴(kuò)展锰镀,實(shí)現(xiàn)encode
方法娘侍。
extension Car {
enum CodingKeys: String, CodingKey {
case owner
case manufacturer
case price
case color
case sizes
}
func encode(to encoder: Encoder) throws {
// 獲取container咖刃,具體解釋見后面
var container = encoder.container(keyedBy: CodingKeys.self)
do {
// 加入編碼數(shù)據(jù),第一項(xiàng)為數(shù)據(jù)的值憾筏,第二項(xiàng)為字段的值
try container.encode(owner, forKey: .owner)
try container.encode(manufacturer, forKey: .manufacturer)
try container.encode("\(price)¥", forKey: .price)
try container.encode(color, forKey: .color)
// 獲取第二種類型的container嚎杨,準(zhǔn)備對(duì)數(shù)組進(jìn)行自定義編碼過程
var sizesContainer = container.nestedUnkeyedContainer(forKey: .sizes)
for size in sizes {
// 自定義編碼數(shù)組過程,將每個(gè)數(shù)組元素翻倍
try sizesContainer.encode(size * size)
}
} catch {
print(error.localizedDescription)
}
}
}
let car = Car(owner: "張三", manufacturer: "比亞迪", price: "336541.23", color: .red, sizes: [1.0, 2.0, 3.0])
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(car)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)
上面代碼中出現(xiàn)了新的事物氧腰,即container
枫浙。什么是container
?原文中并沒有進(jìn)行解釋古拴,可以簡(jiǎn)單理解為管理編碼數(shù)據(jù)的對(duì)象箩帚。
container
有三種類型。
- 有對(duì)應(yīng)鍵值的container黄痪,本質(zhì)上是字典紧帕,最常用的類型。
- 無對(duì)應(yīng)鍵值的container桅打,如數(shù)組是嗜。
- 單一值container,不帶任何類型的元素的原始值挺尾。
在上面代碼中鹅搪,注意container必須是可變屬性,需要使用var聲明遭铺。
解碼
解碼是編碼的逆操作丽柿,大部分操作相似,下面例子是對(duì)車信息的json字符串進(jìn)行解碼掂僵。
let jsonString = """
{"sizes":[1,2,3],"manufacturer":"比亞迪","owner":"張三","price":"336541.23¥","color":"red"}
"""
// 擴(kuò)展car航厚,實(shí)現(xiàn)解碼操作
extension Car {
init(from decoder: Decoder) throws {
do {
let container = try decoder.container(keyedBy: CodingKeys.self)
let owner = try container.decode(String.self, forKey: .owner)
let manufacturer = try container.decode(String.self, forKey: .manufacturer)
let suffixPrice = try container.decode(String.self, forKey: .price)
// 去除價(jià)格后面的人民幣標(biāo)識(shí)
let price = String(suffixPrice[..<(suffixPrice.firstIndex(of: "¥") ?? suffixPrice.endIndex)])
let color = try container.decode(CarColor.self, forKey: .color)
// 準(zhǔn)備解碼數(shù)組
var tempSizes: [Double] = []
// 自定義解碼數(shù)組數(shù)據(jù)
var sizesArray = try container.nestedUnkeyedContainer(forKey: .sizes)
while (!sizesArray.isAtEnd) {
let size = try sizesArray.decode(Double.self)
tempSizes.append(size)
}
self.init(owner: owner, manufacturer: manufacturer, price: price, color: color, sizes: tempSizes)
} catch {
print(error.localizedDescription)
self.init(owner: "", manufacturer: "", price: "", color: .black, sizes: [])
}
}
}
let decoder = JSONDecoder()
let jsonData = jsonString.data(using: .utf8)
let car = try! decoder.decode(Car.self, from: jsonData!)
print(car)
處理繼承關(guān)系
class Person : Codable {
var name: String?
}
class Employee : Person {
var employeeID: String?
}
如果對(duì)Employee編碼的話顷歌,會(huì)出現(xiàn)什么情況锰蓬?答案是只會(huì)編碼Person中的屬性。為此眯漩,我們需要在各自的類中自定義編碼過程芹扭,并實(shí)現(xiàn)相關(guān)調(diào)用。
class Person : Codable {
var name: String?
private enum CodingKeys : String, CodingKey {
case name
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
}
}
class Employee : Person {
var employeeID: String?
private enum CodingKeys : String, CodingKey {
case employeeID = "emp_id"
}
override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder) // 試試不加上這個(gè)語句會(huì)出現(xiàn)什么情況
var container = encoder.container(keyedBy: CodingKeys.self)
// try super.encode(to: container.superEncoder())
try container.encode(employeeID, forKey: .employeeID)
}
}
let employee = Employee()
employee.name = "張三"
employee.employeeID = "000001"
let encoder = JSONEncoder()
let jsonData = try! encoder.encode(employee)
let jsonString = String(data: jsonData, encoding: .utf8)
print(jsonString!)
上面編碼后的結(jié)果如下赦抖。
{
"name": "張三",
"emp_id": "000001"
}
雖然屬性都編碼成功了舱卡,但是結(jié)果是扁平的,我們想改為像繼承一樣具有層級(jí)的json字符串队萤,那么將try super.encode(to: encoder)
改為try super.encode(to: container.superEncoder())
并放在定義container的后面就可以了轮锥,編碼結(jié)果如下。
{
"super": {
"name": "張三"
},
"emp_id": "000001"
}
上面結(jié)果雖然是我們想要的要尔,但是還差點(diǎn)意思舍杜,主要原因在于super不是我們想要的名字新娜,我們想要的是person。
class Employee : Person {
var employeeID: String?
private enum CodingKeys : String, CodingKey {
case employeeID = "emp_id"
// 增加person
case person
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// 指定鍵值
try super.encode(to: container.superEncoder(forKey: .person))
try container.encode(employeeID, forKey: .employeeID)
}
}
總結(jié)
本文只介紹了我經(jīng)常遇到的幾種情況下的使用方法既绩,更多使用技巧和方法請(qǐng)閱讀原文和官方文檔概龄,