Swift中的JSON轉(zhuǎn)換

本文的主要內(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)閱讀原文和官方文檔概龄,

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市饲握,隨后出現(xiàn)的幾起案子私杜,更是在濱河造成了極大的恐慌,老刑警劉巖救欧,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衰粹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颜矿,警方通過查閱死者的電腦和手機(jī)寄猩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骑疆,“玉大人田篇,你說我怎么就攤上這事」棵” “怎么了泊柬?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)诈火。 經(jīng)常有香客問我兽赁,道長(zhǎng),這世上最難降的妖魔是什么冷守? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任刀崖,我火速辦了婚禮,結(jié)果婚禮上拍摇,老公的妹妹穿的比我還像新娘亮钦。我一直安慰自己,他們只是感情好充活,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布蜂莉。 她就那樣靜靜地躺著,像睡著了一般混卵。 火紅的嫁衣襯著肌膚如雪映穗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天幕随,我揣著相機(jī)與錄音蚁滋,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辕录,可吹牛的內(nèi)容都是我干的澄阳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼踏拜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼碎赢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起速梗,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤肮塞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后姻锁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枕赵,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年位隶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拷窜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涧黄,死狀恐怖篮昧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笋妥,我是刑警寧澤懊昨,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站春宣,受9級(jí)特大地震影響酵颁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜月帝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一躏惋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嚷辅,春花似錦簿姨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽深寥。三九已至攘乒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惋鹅,已是汗流浹背则酝。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沽讹。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓般卑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親爽雄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝠检,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容