快速入门

JSON(JavaScript Object Notation)是应用在 JavaScript 语言上的数据格式,常用于网络数据交换和存储。Apple 在Foundation模块中集成了 JSON 格式数据的解析与生成方法,使得 JSON 数据可以快速简单地导入 Swift 代码和 Core Data 中。

本文将以一段 JSON 数据作为示例,着重讲述如何使用基类来处理 JSON 数据的导入。

JSON 数据分析

假设服务器返回了关于 Swift 商店的数据,包含了商店名称、商店地址、开业时间以及其分店信息:

{

"shopName": "Swift Shop",

"address": "2022 Apple Avenue",

"openYear": 2019,

"branches": [

{

"shopName": "Swift UI Experience",

"address": "2019 Apple Avenue",

"branch_manager": "Fiona"

},

{

"shopName": "Codable Protocol",

"address": "2014 iOS Street",

"branch_manager": "Steven",

"closed": true

}

]

}

可以看到这个 JSON 数据除了branch_manager键,其他键命名均为小驼峰规范。第一层是由四个键值对组成的。在branches下,第二层是一个数组并且数据由三个或四个键值对组成,键closed是可选的,默认为false。值得注意的是,第一层和第二层的结构中都有键shopName和address。

简单使用

如果不需要做任何优化或者没有进一步模型需求,那么有一个网站可以满足 JSON 转 Swift 对象的要求:QuickType。将该 JSON 数据复制到网站中即可生成对应的 Swift 模型代码和解析代码,还有其他高级选项可以进一步定制代码:

生成的代码如下:

// This file was generated from JSON Schema using quicktype, do not modify it directly.

// To parse the JSON, add this file to your project and do:

//

// let shopData = try? newJSONDecoder().decode(Shop.self, from: jsonData)

import Foundation

// MARK: - Shop

class Shop: Codable {

var shopName, address: String

let openYear: Int

let branches: [Branch]

init(shopName: String, address: String, openYear: Int, branches: [Branch]) {

self.shopName = shopName

self.address = address

self.openYear = openYear

self.branches = branches

}

}

// MARK: - Branch

class Branch: Codable {

let shopName, address, branchManager: String

let closed: Bool?

enum CodingKeys: String, CodingKey {

case shopName, address

case branchManager = "branch_manager"

case closed

}

init(shopName: String, address: String, branchManager: String, closed: Bool?) {

self.shopName = shopName

self.address = address

self.branchManager = branchManager

self.closed = closed

}

}

可以看到Branch类中比Shop类中多了一个enum CodingKeys...结构。这是因为 Swift 中,默认生成或解析数据的变量命名为小驼峰方式,而Branch中有一个branch_manager键不满足要求,所以需要用CodingKey协议下的枚举对这个键名进行映射,并且其他键名也需要在枚举中列出。

QuickType提供的功能已经可以满足大部分的需求了。如果不同层级下有许多键是重复的,这么做会使用大量的代码,既不美观又不够“聪明”。这种情况下,使用子类可以大大提高数据模型的拓展性,使用泛型处理数据会使代码变得更加轻巧。

进阶模型

根据上文的分析,可以先创建一个 BasicShop 类作为基类:

class BasicShop: Codable {

var shopName: String = ""

var address: String = ""

init() {}

}

与上文的初始化方法不同,基类中通过对变量赋初值来简化init()函数。这一步对于子类的初始化是十分重要的。

根据上述定义的基类,先创建第一层级的模型Shop:

class Shop: BasicShop {

var openYear: Int = 0

var branches: [Branch] = []

private enum CodingKeys: String, CodingKey {

case openYear

case branches

}

override init() {

super.init()

}

required init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

openYear = try container.decode(Int.self, forKey: .openYear)

branches = try container.decode([Branch].self, forKey: .branches)

try super.init(from: decoder)

}

override public func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(openYear, forKey: .openYear)

try container.encode(branches, forKey: .branches)

try super.encode(to: encoder)

}

}

同样的,子类变量需要赋初值,并重写了init()方法;在 Decoder 和 Encoder 的初始化方法中,还需要手动编码对应键值,并调用基类中Codable协议对应的初始化方法。这里注意到有一处与网站生成的代码不同,子类中CodingKeys枚举是必需的,因为在重写 Decoder 和 Encoder 时需要用到对应的映射。为了避免不必要的冲突,CodingKeys枚举被设为private私有的。

class Branch: BasicShop {

var branchManager: String

var closed: Bool?

private enum CodingKeys: String, CodingKey {

case branchManager = "branch_manager"

case closed

}

override init() {

super.init()

}

required init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

branchManager = try container.decode(String.self, forKey: .branchManager)

closed = try container.decodeIfPresent(Bool.self, forKey: .closed)

try super.init(from: decoder)

}

override public func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: CodingKeys.self)

try container.encode(branchManager, forKey: .branchManager)

try container.encode(closed, forKey: .closed)

try super.encode(to: encoder)

}

}

值得注意的是,在解析closed键时,使用的是container.decodeIfPresent方法而不是container.decode方法,后者在无法找到键值时会抛出错误,无法正常进行解析

解析和生成 JSON 数据

解析 JSON 数据:

let jsonData = jsonString.data(using: .utf8)

let shopData = try? JSONDecoder().decode(Shop.self, from: jsonData!)

生成 JSON 数据:

let jsonData = try JSONEncoder().encode(shopData)

let jsonString = String(data: jsonData, encoding: .utf8)

参考

QuickType Pietar-Jan Nefkens

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: