Swift中的JSON轉(zhuǎn)換

本文的主要內(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)常遇到的幾種情況下的使用方法,更多使用技巧和方法請閱讀原文和官方文檔,

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容