使用 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 相匹配。