本文的主要內(nèi)容來自于https://benscheirman.com/2017/06/swift-json.html,并在此基礎(chǔ)上進(jìn)行了翻譯和精簡。更多細(xì)節(jié)可以直接閱讀原文。
本文中的Swift版本為5.8
快速開始
有些朋友不喜歡長篇大論的理論和原理講解,想快速掌握大概的使用方法,所以我在這里舉了兩個例子,以滿足這方面的需求。
/*
編碼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é)構(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 {
// 因為編解碼可能會失敗,所以必須要使用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的流程與上面類似,可嘗試自己編寫。
自定義字段名
有時候我們并不想把變量名稱或常量名稱當(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中并沒有對應(yīng)的日期類型,如果直接編碼的話,會輸出一串?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,具體使用方法請查閱相關(guān)文檔。
處理URL
假設(shè)有如下json字符串。
{
"title": "百度一下",
"url": "http://baidu.com"
}
那么對應(yīng)的struct為如下。
struct Webpage: Codable {
var title: String
var url: URL
}
解碼過程也是相當(dāng)?shù)暮唵?,如下所示?/p>
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é)構(gòu)組成,所以在定義這些json結(jié)構(gòu)時,可以使用嵌套完成復(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)的控制編解碼過程,那么就需要自定義編解碼過程。
編碼
這里以車為例子,里面包含一些基本信息,有些信息沒有實際意義,僅僅為了演示需要。
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]
}
然后對車進(jìn)行擴(kuò)展,實現(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ù),第一項為數(shù)據(jù)的值,第二項為字段的值
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)備對數(shù)組進(jìn)行自定義編碼過程
var sizesContainer = container.nestedUnkeyedContainer(forKey: .sizes)
for size in sizes {
// 自定義編碼數(shù)組過程,將每個數(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)行解釋,可以簡單理解為管理編碼數(shù)據(jù)的對象。
container有三種類型。
- 有對應(yīng)鍵值的container,本質(zhì)上是字典,最常用的類型。
- 無對應(yīng)鍵值的container,如數(shù)組。
- 單一值container,不帶任何類型的元素的原始值。
在上面代碼中,注意container必須是可變屬性,需要使用var聲明。
解碼
解碼是編碼的逆操作,大部分操作相似,下面例子是對車信息的json字符串進(jìn)行解碼。
let jsonString = """
{"sizes":[1,2,3],"manufacturer":"比亞迪","owner":"張三","price":"336541.23¥","color":"red"}
"""
// 擴(kuò)展car,實現(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)
// 去除價格后面的人民幣標(biāo)識
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?
}
如果對Employee編碼的話,會出現(xiàn)什么情況?答案是只會編碼Person中的屬性。為此,我們需要在各自的類中自定義編碼過程,并實現(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) // 試試不加上這個語句會出現(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é)果是扁平的,我們想改為像繼承一樣具有層級的json字符串,那么將try super.encode(to: encoder)改為try super.encode(to: container.superEncoder())并放在定義container的后面就可以了,編碼結(jié)果如下。
{
"super": {
"name": "張三"
},
"emp_id": "000001"
}
上面結(jié)果雖然是我們想要的,但是還差點意思,主要原因在于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)常遇到的幾種情況下的使用方法,更多使用技巧和方法請閱讀原文和官方文檔,