如何在 Swift 中使用 JSON

原文:How to Work With JSON in Swift

介绍

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

本文是由两部分组成的系列。第一部分将介绍 JSON 是什么以及它在 Swift 中的基本处理方式 ,而第二部分将重点介绍通过 URLSession 和 Alamofire 从网络中检索 JSON 数据。

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

什么是 JSON?

JSON 代表 JavaScript 对象表示法(JavaScript Object Notation)。 JSON 是一种用于存储和传输数据的轻量级格式。 JSON 格式由 key 和 value 组成。在 Swift 中,将这种格式视为 Dictionary,其中每个 key 必须是唯一的,value 可以是 String、Number、Bool 或 null(空)。value 也可以是另一个 JSON 对象或上述类型的数组,但让我们从简单开始。 JSON 可以在 Swift 中存储为多行字符串并转换为 Data 对象(反之亦然)。这是通过 Decodable 和 Encodable 协议完成的。

下面是一个带有两个 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 的处理变得简单直接 ------Decodable、Encodable 和 Codable。 Codable 是 Decodable 和 Encodable 的类型别名,它是通过 Swift 的协议组合完成的。让我们逐一分析。

Encodable

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

Decodable

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

Codable

swift 复制代码
// Codable 协议是 Decodable 和 Encodable 协议的组合
typealias Codable = Decodable & Encodable

正如本文前面提到的,Codable 是 Encodable 和 Decodable 的类型别名,它允许你的类型用于编码和解码 JSON 对象。 Codable 是在 Swift 4 中引入的。无论你想要将类型标记为 Encodable、Decodable 还是 Codable,都取决于你。有些人可能更喜欢明确命名并坚持使用 Encodable 和 Decodable,但除非另有说明,否则只需标记为 Codable。

Coding Keys

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

  • camelCase,驼峰式命名法(如:userName、fileName、phoneNumber)
  • snake_case,蛇形命名法(如:user_name、file_name、phone_number)
  • Pascal_Case,帕斯卡命名法(如:UserName、FileName、PhoneNumber)

某些 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 的类 - JSONDecoder 和 JSONEncoder。好吧,从技术上来说,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 对象。

相关推荐
Coder_张三14 小时前
iOS 26 适配
前端·ios·swift
用户380341658829714 小时前
崩溃率从 0.3% 到 0.04%:我们做对的不是修 bug,是让每个崩溃只发生一次
ios·swift
东坡肘子21 小时前
当 Build in Public 重新转入地下 -- 肘子的 Swift 周报 #157
人工智能·swiftui·swift
用户38034165882972 天前
Flutter 迁 SwiftUI:AI 把页面"搬"过来只用了两周,我们补结构花了两个月
ios·swift
茶底世界之下2 天前
四角变换后的最小包围视口,为什么不能只把宽高取整?
ios·swift
丨白色风车丨2 天前
Python中JSON与字典/列表的相互转换详解
windows·python·json
鱼宵3 天前
LangChain4j 结构化输出:让模型吐出 Java 对象,JSON 不再手写解析
java·开发语言·json·langchain4j
茶底世界之下3 天前
预乘 Alpha 图层合成为什么会越叠越暗?
架构·swift
leisoo80973 天前
融资融券数据怎么查两融指标含义与杠杆观察方法 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·json
胖头鱼的鱼缸(尹海文)4 天前
胖头鱼的技术专栏-474 从能跑到能上线:PG SplitJSON 0.2.0 和 0.1.0 差在哪(20261008)
postgresql·json