原文: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 目标:
{
"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 的处理变得简略直接 ——Decodable
、Encodable
和 Codable
。 Codable
是 Decodable
和 Encodable
的类型别号,它是经过 Swift 的协议组合完结的。让咱们逐个分析。
Encodable
经过将模型类型标记为 Encodable
,告诉 Swift 你想要运用此类型将 Person
的实例转换为 JSON 目标。最常见的场景是:将数据发送到服务器。
Decodable
经过将模型类型标记为 Decodable
,告诉 Swift 你想要运用此类型将 JSON 转换为 Person
实例。最常用于从服务器接纳数据时。
Codable
// 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
的枚举类型:
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 的类 – JSONDecoder
和 JSONEncoder
。好吧,从技术上来说,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)")
}
- 咱们的模型,咱们将用它来把 JSON 字符串转换为 Swift 类型。
- 将 JSON 字符串转换为 Data 数据类型。
- 创立一个 JSON 解码器的实例。
- 不运用
CodingKey
协议,在JSONDecoder
类上有一个名为keyDecodingStrategy
的特点,能够将格局为 snake_case 的 JSON 键转换为 Swift 偏好的 camelCase。很好,很简略! -
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 目标。