引言
在处理JSON数据时,Swift4中有个强大的协议 Codable
,可以用于数据解析(不懂的可以看下Codable的基本功能)。这里,结合实践,总结swift中 JSON 数据的处理,主要包括:
- 基本的解析和对象化
- 对象化进阶
- 其它:错误处理
JSON数据的序列化和对象化
一个简单的 JSON 数据:
{
"taskName": "Solor",
"taskNumber": 308001,
"starDate": "05-30-2019",
"isAssigned": false
}
可以使用 JSONSerialization
进行序列化:
var jsonData = jsonString.data(using: .utf8)!
var task = try JSONSerialization.jsonObject(with: jsonData, options: .mutableLeaves)
if let taskdic = task as? Dictionary<String, Any> {
taskdic["taskName"]
}
而在实际开发中,我们往往会根据数据定义一个 model,来更好地进行面向对象的开发:
struct ZZHTask {
let taskName: String
let taskNumber: Int
let startDate: String
let isAssigned: Bool
}
extension ZZHTask: Decodable {
}
如上,定义一个struct,并实现Decodable
协议,接下来就可以解析json数据并生成对象:
let decoder = JSONDecoder()
task = try decoder.decode(ZZHTask.self, from: jsonData)
如果JOSN数据中使用的是下划线分割,那在定义model的时候,可以使用CodingKey
做映射:
extension ZZHTask: Decodable {
enum CodingKeys: String, CodingKey {
case taskName
case taskNumber
case startDate
case isAssigned
case taskMission = "task_mission"
}
}
此时对应的 JSON 需要添加: "task_mission": "this is to do something"
。
或许你会注意到,model中 startDate
类型是 String,而显然它应该是
Date
对象。由于JSON中日期字符串的格式只包含日月年,这时需要指定一个format并赋值给 decoder:
extension DateFormatter {
static let customerFormat: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy"
return formatter
}()
}
decoder.dateDecodingStrategy = .formatted(DateFormatter.customerFormat)
于是,在解析Date
类型的时候, decoder
会使用上述的 customerFormat
进行解析。
进阶用法
这部分针对的是对象化。JSON 数据需要做序列化还是对象化不是本文的讨论内容,我秉承的简单原则:如果数据需要在多处使用,那么做对象化。
嵌套
假如JSON内容变为:
{
"taskName": "Solor",
"taskNumber": 100105,
"startDate": "05-30-2019",
"isAssigned": false,
"task_mission": "this is to do something",
"taskConditions": {
"load": 5000,
"temperature": 50,
"pressure": 1200
}
}
这时需要创建一个新 model,而 decode 的部分无需修改:
struct ZZHCondition {
let load: Int
let temperature: Int
let pressure: Int
}
extension ZZHCondition: Decodable {
}
//在 ZZHTask 中添加
let taskCondition: ZZHCondition
//在 ZZHTask extension 中添加
case taskCondition
扁平化
继续修改json数据:
{
"taskName": "Solor",
"taskNumber": 100105,
"startDate": "05-30-2019",
"isAssigned": false,
"task_mission": "this is to do something",
"taskCondition": {
"load": 5000,
"temperature": 50,
"pressure": 1200
},
"assigner": {
"csr_name": "Wang Gang"
}
}
这里添加了 assigner
, 其值是一个键值对。我们完全可以照此新添加model类 Assigner
,但从业务出发,我们可以理解assigner
的值直接是 Wang Gang
会更加恰当。因此,我们修改 ZZHTask
:
struct ZZHTask {
let taskName: String
let taskNumber: Int
let startDate: Date
let isAssigned: Bool
let taskMission: String
let taskCondition: ZZHCondition
let assigner: String
}
因为 assigner
对应的并不是一个 String
,decode的时候需要做一些修改:将 csr_name
对应的值赋给 assigner。
extension ZZHTask: Decodable {
enum CodingKeys: String, CodingKey {
case taskName
case taskNumber
case startDate
case isAssigned
case taskMission = "task_mission"
case taskCondition
case assigner
enum AssignerKey: String, CodingKey {
case csrName = "csr_name"
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
taskName = try container.decode(String.self, forKey: .taskName)
taskNumber = try container.decode(Int.self, forKey: .taskNumber)
startDate = try container.decode(Date.self, forKey: .startDate)
isAssigned = try container.decode(Bool.self, forKey: .isAssigned)
taskMission = try container.decode(String.self, forKey: .taskMission)
taskCondition = try container.decode(ZZHCondition.self, forKey: .taskCondition)
let assignContainer = try container.nestedContainer(keyedBy: CodingKeys.AssignerKey.self, forKey: .assigner)
assigner = try assignContainer.decode(String.self, forKey: .csrName)
}
}
首先,我们在 ZZHTask
中添加了属性 assigner
;对应在CodingKeys
中添加相同的case,以满足 Decodable
协议。你也可以看到,extension中添加了 AssignerKey
,这是用来decode嵌套的assign
对象。因为此时model ZZHTask
的属性和JSON数据不是一一对应的,所以需要手动实现 init(from decoder: Decoder)
。其中,和assigner
相关的部分就是:
let assignContainer = try container.nestedContainer(keyedBy: CodingKeys.AssignerKey.self, forKey: .assigner)
assigner = try assignContainer.decode(String.self, forKey: .csrName)
先获取该嵌套对象对应的容器,再通过key去解析容器中对应的值,并赋给 assigner
属性。
数组
关于Array,先看简单的情形,假如在JSON中添加键值:
"parts":[
{"number": "WD062781", "name": "fep"},
{"number": "WF065212", "name": "ltp"}
],
这种情形,可以创建一个model,如:
struct ZZHPart {
let partNumber: String
let partName: String
}
extension ZZHPart: Decodable {
enum CodingKeys: String, CodingKey {
case partNumber = "number"
case partName = "name"
}
}
然后在ZZHTask
中添加属性: let parts: [ZZHPart]
,并更新 extension:
case parts
parts = try container.decode([ZZHPart].self, forKey: .parts)
接下来看下稍微复杂点的情形,现在我们的JSON中添加了新的内容:
"workers": {
"site": [
{"worker": { "id": "W60001", "name": "Li"}},
{"worker": { "id": "W60023", "name": "Wan"}}
]
},
"sales":[
{"worker":{"id": "S80010","name": "Cang"}}
]
如果把site
节点去掉,workers
和sales
两个节点都是包含worker
的数组,并且worker
节点也可以扁平化处理(sales
和parts
的不同就在于扁平化)。首先还是先创建一个model:
struct ZZHWorker {
let workerID: String
let workerName: String
}
添加model的decodable extension:
extension ZZHWorker: Decodable {
enum CodingKeys: String, CodingKey {
case workerID = "id"
case workerName = "name"
}
enum WorkerKey: CodingKey { case worker }
init(from decoder: Decoder) throws {
let rootKeys = try decoder.container(keyedBy: WorkerKey.self)
let workerContainer = try rootKeys.nestedContainer(keyedBy: CodingKeys.self, forKey: .worker)
workerID = try workerContainer.decode(String.self, forKey: .workerID )
workerName = try workerContainer.decode(String.self, forKey: .workerName)
}
}
把worker
节点扁平化的工作放在了model中进行,因为这样处理workers
节点的时候,可以复用。接下来,需要在 ZZHTask
添加相应的属性,并且在 extension 中更新:
//ZZHTask
let sales:[ZZHWorker]
//ZZHTask extention
case sales
//ZZHTask extention decoder方法中
var saleContainer = try container.nestedUnkeyedContainer(forKey: .sales)
var saleTmp: [ZZHWorker] = []
while !saleContainer.isAtEnd {
let work = try saleContainer.decode(ZZHWorker.self)
saleTmp += [work]
}
sales = saleTmp
和之前不太一样的是,数组需要使用 nestedUnkeyedContainer
来处理。worker
的节点处理,是类似的流程,你可以自己实践下,或者查看示例代码。
其它
错误处理
对于对象化和序列化,都需要通过 do-catch
处理异常。对于对象化的处理方式,注意属性要设置成optional
(上文例子不适用于生产环境)。
对于序列化的处理方式,在取键值的时候,或者使用if或者optional。如果JSON有很多层嵌套的话,那会让人很崩溃。这时,可以使用SwiftyJson,极大地提高代码简洁度。
model的创建
也许你会问对象化处理过程中,需要创建的model太多。不用担心,如ObjC
时代,亦有很多的工具来生产Swift中使用的model——struct或class。这里,推荐 JSONExport。
Comments