如何在 Swift 中使用 JSON

原文: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 对象:

json 复制代码
{
    "name": "Josh",
    "age": 30
}

创建一个模型

将 JSON 对象转换为 Swift 类型的第一步是创建模型。对于我们上面的例子,这里有一个我们可以使用的结构:

swift 复制代码
struct Person: Codable {
    var name: String
    var age: Int
}

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

请务必阅读 API 文档。在某些情况下,key 可能有值也可能没有值。如果是这种情况,你应该将该属性标记为 Optional 可选类型。

嵌套的 JSON 对象

你肯定会遇到嵌套 JSON 对象的情况。这是处理此问题的直接方法:

swift 复制代码
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

swift 复制代码
// 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 的枚举类型:

swift 复制代码
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 字符串来帮助更清楚地说明这个过程。

swift 复制代码
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 类型的步骤:

swift 复制代码
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 对象。

swift 复制代码
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

swift 复制代码
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

swift 复制代码
//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

swift 复制代码
//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 对象。

相关推荐
Json_181790144805 小时前
商品详情接口使用方法和对接流程如下
大数据·json
ZhongruiRao1 天前
Springboot+PostgreSQL+MybatisPlus存储JSON或List、数组(Array)数据
spring boot·postgresql·json
华农第一蒟蒻1 天前
Java中JWT(JSON Web Token)的运用
java·前端·spring boot·json·token
胡耀超1 天前
知识图谱入门——8: KG开发常见数据格式:OWL、RDF、XML、GraphML、JSON、CSV。
xml·json·知识图谱·csv·owl·graphml·gml
x-cmd1 天前
[241005] 14 款最佳免费开源图像处理库 | PostgreSQL 17 正式发布
数据库·图像处理·sql·安全·postgresql·开源·json
先知demons2 天前
js将对象的键和值分别归纳进对象,并将多层对象转化成数据的方法
javascript·vue.js·json
一丝晨光2 天前
继承、Lambda、Objective-C和Swift
开发语言·macos·ios·objective-c·swift·继承·lambda
Midsummer啦啦啦2 天前
Python字符串转JSON格式指南
开发语言·python·json
前端 贾公子2 天前
Express内置的中间件(express.json和express.urlencoded)格式的请求体数据
中间件·json·express