原文:How to Work With JSON in Swift

介绍

JSON 是在服务器和设备之间传输数据的最常见的数据格局之一。

本文是由两部分组成的系列。榜首部分将介绍 JSON 是什么以及它在 Swift 中的根本处理方式,而第二部分将重点介绍经过 URLSessionAlamofire 从网络中检索 JSON 数据

假如您想持续学习,请从我的 GitHub 下载示例项目。

什么是 JSON?

JSON 代表 JavaScript 目标表明法(JavaScript Object Notation)。 JSON 是一种用于存储和传输数据的轻量级格局。 JSON 格局由 key 和 value 组成。在 Swift 中,将这种格局视为 Dictionary,其中每个 key 有必要是唯一的,value 能够是 StringNumberBoolnull(空)。value 也能够是另一个 JSON 目标或上述类型的数组,但让咱们从简略开端。 JSON 能够在 Swift 中存储为多行字符串并转换为 Data 目标(反之亦然)。这是经过 DecodableEncodable 协议完结的。

下面是一个带有两个 key 的简略 JSON 目标:

{
    "name": "Josh",
    "age": 30
}

创立一个模型

将 JSON 目标转换为 Swift 类型的榜首步是创立模型。关于咱们上面的比如,这里有一个咱们能够运用的结构:

struct Person: Codable {
    var name: String
    var age: Int
}

如你所见,每个 JSON 键都将由上述模型中的特点表明。请必须遵守 Codable 协议,以便它可用于解码和编码 JSON。

请必须阅读 API 文档。在某些状况下,key 或许有值也或许没有值。假如是这种状况,你应该将该特点标记为 Optional 可选类型。

嵌套的 JSON 目标

你肯定会遇到嵌套 JSON 目标的状况。这是处理此问题的直接办法:

let person = """
{
    "name": "Josh",
    "age": 30,
    "birthplace": {
        "latitude": 37.3326,
        "longitude": 122.0055
    }
}
"""
struct Person: Codable {
    var name: String
    var age: Int
    var birthplace: Birthplace
    struct Birthplace: Codable {
        var latitude: Double
        var longitude: Double
    }
}

Decodable 和 Encodable 协议

Swift 有三种协议,能够使 JSON 的处理变得简略直接 ——DecodableEncodableCodableCodableDecodableEncodable 的类型别号,它是经过 Swift 的协议组合完结的。让咱们逐个分析。

Encodable

经过将模型类型标记为 Encodable,告诉 Swift 你想要运用此类型将 Person 的实例转换为 JSON 目标。最常见的场景是:将数据发送到服务器。

Decodable

经过将模型类型标记为 Decodable,告诉 Swift 你想要运用此类型将 JSON 转换为 Person 实例。最常用于从服务器接纳数据时。

Codable

// Codable 协议是 Decodable 和 Encodable 协议的组合
typealias Codable = Decodable & Encodable

正如本文前面说到的,CodableEncodableDecodable 的类型别号,它答应你的类型用于编码和解码 JSON 目标。 Codable 是在 Swift 4 中引入的。无论你想要将类型标记为 EncodableDecodable 还是 Codable,都取决于你。有些人或许更喜欢明确命名并坚持运用 EncodableDecodable,但除非还有阐明,不然只需标记为 Codable

Coding Keys

Swift 中的特点命名约好是驼峰式(camelCase)命名。以下是编程中常见命名约好的几个示例:

  • camelCase,驼峰式命名法(如:userNamefileNamephoneNumber
  • snake_case,蛇形命名法(如:user_namefile_namephone_number
  • Pascal_Case,帕斯卡命名法(如:UserNameFileNamePhoneNumber

某些 API 接口的 key 不是驼峰式大小写格局。为了坚持代码整齐并遵循 Swift 约好,Swift 提供了 CodingKey 协议。该协议将告诉你的程序运用自定义 key,同时坚持 camelCase 约好。约好是在类型中创立一个名为 CodingKeys 的枚举类型:

let person = """
{
    "name": "Josh",
    "age": 30,
    "full_name_of_person": "Josh Smith"
}
"""
//Step 1 - Create a model
struct Person: Codable {
    var name: String
    var age: Int
    var fullName: String
    enum CodingKeys: String, CodingKey {
        case name
        case age
        case fullName = "full_name_of_person"   // 此字符串值应与对应的 key 彻底匹配
    }
}

自定义解码

有时你或许想把 JSON 扁平化为一个单一的类型。这能够经过创立一个自定义解码结构器来完成。我创立了一个新的类型和 JSON 字符串来帮助更清楚地阐明这个进程。

let account = """
{
    "name": "Capital One",
    "balances": {
        "current": 37103.45,
        "available": 1024.55
    }
}
"""
//Step 1 - Create a model
struct Account: Decodable {
    var name: String
    var currentBalance: Double
    var availableBalance: Double
    enum CodingKeys: String, CodingKey {
        case name, balances
    }
    enum BalancesCodingKeys: String, CodingKey {
        case currentBalance = "current"
        case availableBalance = "available"
    }
    // 自定义解码,将嵌套的 JSON 结构扁平化
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        let nestedContainer = try container.nestedContainer(keyedBy: BalancesCodingKeys.self, forKey: .balances)
        currentBalance = try nestedContainer.decode(Double.self, forKey: .currentBalance)
        availableBalance = try nestedContainer.decode(Double.self, forKey: .availableBalance)
    }
}
let jsonDecoder = JSONDecoder()
let accountData = Data(account.utf8)
do {
    let decodedAccount = try jsonDecoder.decode(Account.self, from: accountData)
    print("Account: (decodedAccount.name) has a balance of (decodedAccount.currentBalance)")
} catch {
    print(error.localizedDescription)
}

这种办法并不局限于一个层次。你能够深入到几个层次,但它会很快变得复杂,可读性或许会受到影响。

JSONDecoder 和 JSONEncoder

Swift 有两个处理 JSON 的类 – JSONDecoderJSONEncoder。好吧,从技术上来说,Swift 还有第三个,JSONSerialization,但咱们将只运用我刚才说到的两个。

JSONDecoder

解码答应将一个 JSON 目标转换为 Swift 类型。运用咱们上面的比如,让咱们创立一个 JSONDecoder 的实例。下面是将一个 JSON 字符串转换为咱们的 Person 类型的步骤:

let person = """
{
    "name": "Josh",
    "age": 30,
    "full_name": "Josh Smith"
}
"""
//1 - 创立一个模型
struct Person: Codable {
    var name: String
    var age: Int
    var fullName: String
}
//2 - 将字符串转换为 Data 类型
let personData = Data(person.utf8)
//3 - 创立一个 JSONDecoder 实例
let jsonDecoder = JSONDecoder()
//4 - 运用 JSONDecoder 实例内置的解码策略
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
//5 - 运用 JSONDecoder 实例将 JSON 目标解码为 Person 目标
do {
    let decodedPerson = try jsonDecoder.decode(Person.self, from: personData)
    print("Person -- (decodedPerson.name) was decode and their age is: (decodedPerson.age)")
} catch {
    print("Error: (error.localizedDescription)")
}
  1. 咱们的模型,咱们将用它来把 JSON 字符串转换为 Swift 类型。
  2. 将 JSON 字符串转换为 Data 数据类型。
  3. 创立一个 JSON 解码器的实例。
  4. 不运用 CodingKey 协议,JSONDecoder 类上有一个名为 keyDecodingStrategy 的特点,能够将格局为 snake_case 的 JSON 键转换为 Swift 偏好的 camelCase。很好,很简略!
  5. JSONDecoder 有一个解码办法,能够将咱们的数据目标转换为咱们想要的 Swift 类型。这个办法是一个可抛出反常办法,所以应该在 do-catch 语句中处理。

JSONEncoder

编码答应将一个 Swift 类型转换为有效的 JSON 目标。

struct Person: Codable {
    var name: String
    var age: Int
}
let person = Person(name: "Josh", age: 30)
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
do {
    let encodePerson = try jsonEncoder.encode(person)
    let endcodeStringPerson = String(data: encodePerson, encoding: .utf8)!
    print(endcodeStringPerson)
} catch {
    print(error.localizedDescription)
}

outputFormatting 特点设置为.prettyPrinted 能够使 JSON 在打印到控制台时变得漂亮。

格局化日期

iso8601 – 将日期格局化为相似:1990–12–15T00:00:00Z。这将是 JSON 目标中的一个字符串类型。要将此值转换为 Swift Date 目标,将 dateDecodingStrategy 设置为.iso8601

struct Person: Codable {
    var name: String
    var age: Int
    var birthDate: Date
}
//iso8601 date format
let person = """
{
    "name": "Josh",
    "age": 30,
    "birthDate": "1990-12-15T00:00:00Z",
}
"""
let jsonDecoder = JSONDecoder()
jsonDecoder.dateDecodingStrategy = .iso8601 //1990-12-15T00:00:00Z
let personData = Data(person.utf8)
do {
    let decodedPerson = try jsonDecoder.decode(Person.self, from: personData)
    print("Person -- (decodedPerson.name) was decode and their birthdate is: (decodedPerson.birthDate)")
} catch {
    print("Error: (error.localizedDescription)")
}

secondsSince1970 – 将日期格局化为相似:661219200. 这将是 JSON 目标中的一个 Int 类型。要将此值转换为 Swift Date 目标,将 dateDecodingStrategy 设置为.secondsSince1970

//secondsSince1970 date format
let person2 = """
{
    "name": "Josh",
    "age": 30,
    "birthDate": 661219200,
}
"""
let jsonDecoder2 = JSONDecoder()
jsonDecoder2.dateDecodingStrategy = .secondsSince1970  //661219200  --> Note - must be an INT
let personData2 = Data(person2.utf8)
do {
    let decodedPerson = try jsonDecoder2.decode(Person.self, from: personData2)
    print("Person -- (decodedPerson.name) was decode and their birthdate is: (decodedPerson.birthDate)")
} catch {
    print("Error: (error.localizedDescription)")
}

Custom — 一个日期能够用几种不同的方式格局化为一个字符串。这就是需要自定义选项的地方。创立一个 DateFormatter 类的实例,用代表数据格局的自定义字符串设置 dateFormat

//Custom date format
let person3 = """
{
    "name": "Josh",
    "age": 30,
    "birthDate": "1990/12/15",
}
"""
let jsonDecoder3 = JSONDecoder()
// 自定义日期格局
let dateFomatter = DateFormatter()
dateFomatter.dateFormat = "yyyy/MM/dd"  //1990/12/15
jsonDecoder3.dateDecodingStrategy = .formatted(dateFomatter)
let personData3 = Data(person3.utf8)
do {
    let decodedPerson = try jsonDecoder3.decode(Person.self, from: personData3)
    print("Person -- (decodedPerson.name) was decode and their birthdate is: (decodedPerson.birthDate)")
} catch {
    print("Error: (error.localizedDescription)")
}

总结

如你所见,在 Swift 中处理 JSON 是十分容易的。嵌套的 JSON 能够很快变得复杂,但至少你在这种状况下有多个选择。正如我上面说到的,这将是一个两部分的系列。在下一篇文章中,我将演示怎么运用 URLSession 和第三方网络库 Alamofire 从网上检索 JSON 目标。