原文: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)")
}
- 我们的模型,我们将用它来把 JSON 字符串转换为 Swift 类型。
- 将 JSON 字符串转换为 Data 数据类型。
- 创建一个 JSON 解码器的实例。
- 不使用
CodingKey
协议,在JSONDecoder
类上有一个名为keyDecodingStrategy
的属性,可以将格式为 snake_case 的 JSON 键转换为 Swift 偏好的 camelCase。很好,很简单! 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 对象。