使用 Apple Foundation 和 Swift 標準庫進行 JSON 序列化編碼和解碼

JSONSerialization 類是內建到蘋果的基礎框架。

Version = 2.2

閱讀 JSON

JSONObjectWithData 函式接受 NSData,並返回 AnyObject。你可以使用 as? 將結果轉換為你期望的型別。

do {
    guard let jsonData = "[\"Hello\", \"JSON\"]".dataUsingEncoding(NSUTF8StringEncoding) else {
        fatalError("couldn't encode string as UTF-8")
    }

    // Convert JSON from NSData to AnyObject
    let jsonObject = try NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
    
    // Try to convert AnyObject to array of strings
    if let stringArray = jsonObject as? [String] {
        print("Got array of strings: \(stringArray.joinWithSeparator(", "))")
    }
} catch {
    print("error reading JSON: \(error)")
}

當頂級物件不是陣列或字典時,你可以傳遞 options: .AllowFragments 而不是 options: [] 以允許讀取 JSON。

寫 JSON

呼叫 dataWithJSONObject 將與 JSON 相容的物件(帶有字串,數字和 NSNull 的巢狀陣列或字典)轉換為以 UTF-8 編碼的原始 NSData

do {
    // Convert object to JSON as NSData
    let jsonData = try NSJSONSerialization.dataWithJSONObject(jsonObject, options: [])
    print("JSON data: \(jsonData)")
    
    // Convert NSData to String
    let jsonString = String(data: jsonData, encoding: NSUTF8StringEncoding)!
    print("JSON string: \(jsonString)")
} catch {
    print("error writing JSON: \(error)")
}

你可以通過 options: .PrettyPrinted 而不是 options: [] 進行漂亮列印。

Version = 3.0

Swift 3 中的行為相同但語法不同。

do {
    guard let jsonData = "[\"Hello\", \"JSON\"]".data(using: String.Encoding.utf8) else {
        fatalError("couldn't encode string as UTF-8")
    }
    
    // Convert JSON from NSData to AnyObject
    let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
    
    // Try to convert AnyObject to array of strings
    if let stringArray = jsonObject as? [String] {
        print("Got array of strings: \(stringArray.joined(separator: ", "))")
    }
} catch {
    print("error reading JSON: \(error)")
}

do {
    // Convert object to JSON as NSData
    let jsonData = try JSONSerialization.data(withJSONObject: jsonObject, options: [])
    print("JSON data: \(jsonData)")

    // Convert NSData to String
    let jsonString = String(data: jsonData, encoding: .utf8)!
    print("JSON string: \(jsonString)")
} catch {
    print("error writing JSON: \(error)")
}

注意:以下內容目前僅適用於 Swift 4.0 及更高版本。

從 Swift 4.0 開始,Swift 標準庫包含 EncodableDecodable 協議,以定義資料編碼和解碼的標準化方法。採用這些協議將允許 EncoderDecoder 協議的實現獲取你的資料,並將其編碼或解碼到外部表示(如 JSON)和從外部表示解碼。符合 Codable 協議結合了 EncodableDecodable 協議。現在,這是在程式中處理 JSON 的推薦方法。

自動編碼和解碼

使型別可編碼的最簡單方法是將其屬性宣告為已經知道的型別。這些型別包括標準庫型別,如 StringIntDouble; 和基礎型別,如 DateDataURL。如果型別的屬性是可編碼的,則型別本身將通過簡單地宣告一致性自動符合 Codable

考慮以下示例,其中 Book 結構符合 Codable

struct Book: Codable {
    let title: String
    let authors: [String]
    let publicationDate: Date
}

請注意,如果 ArrayDictionary 等標準集合包含可編碼型別,則它們符合 Codable

通過採用 CodableBook 結構現在可以使用 Apple Foundation 類 JSONEncoderJSONDecoder 編碼到 JSON 並從 JSON 解碼,即使 Book 本身不包含專門處理 JSON 的程式碼。也可以通過分別符合 EncoderDecoder 協議來編寫自定義編碼器和解碼器。

編碼為 JSON 資料

// Create an instance of Book called book
let encoder = JSONEncoder()
let data = try! encoder.encode(book) // Do not use try! in production code
print(data)

設定 encoder.outputFormatting = .prettyPrinted 以便於閱讀。 ##從 JSON 資料解碼

從 JSON 資料解碼

// Retrieve JSON string from some source
let jsonData = jsonString.data(encoding: .utf8)!
let decoder = JSONDecoder()
let book = try! decoder.decode(Book.self, for: jsonData) // Do not use try! in production code
print(book)

在上面的例子中,Book.self 通知解碼器應該解碼 JSON 的型別。

獨佔編碼或解碼

有時你可能不需要資料既可編碼又可解碼,例如當你只需要從 API 讀取 JSON 資料時,或者你的程式僅將 JSON 資料提交給 API 時。

如果你只想編寫 JSON 資料,請將你的型別與 Encodable 保持一致。

struct Book: Encodable {
    let title: String
    let authors: [String]
    let publicationDate: Date
}

如果你只想閱讀 JSON 資料,請將你的型別符合 Decodable

struct Book: Decodable {
    let title: String
    let authors: [String]
    let publicationDate: Date
}

使用自定義鍵名稱

API 經常使用除 Swift 標準駝峰案例之外的命名約定,例如蛇案例。在解碼 JSON 時,這可能會成為一個問題,因為預設情況下,JSON 鍵必須與你的型別的屬性名稱完全對齊。要處理這些情況,你可以使用 CodingKey 協議為你的型別建立自定義鍵。

struct Book: Codable {
    // ...
    enum CodingKeys: String, CodingKey { 
        case title
        case authors
        case publicationDate = "publication_date"
    }
}

CodingKeys 是針對採用 Codable 協議的型別自動生成的,但是通過在上面的示例中建立我們自己的實現,我們允許我們的解碼器將本地駝峰情況 publicationDate 與由蛇提供的蛇盒 publication_date 相匹配。