文 / 菲拉兔
要求:
- Platform: iOS8.0+
- Language: Swift4.0
- Editor: Xcode9
【問題補(bǔ)充2017-09-28】
最近我發(fā)現(xiàn)了一個(gè)問題:在Swift4.0中對(duì)JSON數(shù)據(jù)進(jìn)行解析的時(shí)候尝盼,如果還用老的
JSONSerialization
類的話鲤桥,會(huì)出現(xiàn)一個(gè)BUG:
- 問題: 比如我有一個(gè)
NSObject
的類叫Student
腺阳,其中包含一個(gè)var name = ""
屬性骤铃,那么在以上方法解析JSON數(shù)據(jù)的過程中一忱,name的值將不被寫入许饿,這應(yīng)該是Swift4.0的一個(gè)BUG胖烛;- 解決方法:
- 用其他的名字替代
name
字段(暫時(shí)發(fā)現(xiàn)只有對(duì)這個(gè)屬性不起作用)产雹,例如var sname = ""
谬莹;
- 用
JSONDecoder
新的方式去解析檩奠;
Swift4.0以前和OC時(shí)代的JSON數(shù)據(jù)處理
Swift(1..<4)& Objective-C
Swift4.0以前的JSON解析/編碼约素,和OC時(shí)代一樣,都是通過NSJSONSerialization類的一些類方法進(jìn)行處理的
- JSON解析
struct GroceryProduct{
var name: String
var points: Int
var description: String
}
// 數(shù)據(jù)獲取
guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),
let data = try? Data.init(contentsOf: fileURL) else{
fatalError("`JSON File Fetch Failed`")
}
// JSON序列化
guard let json = try? JSONSerialization.jsonObject(with: data, options: .mutableContainers),
let array = json as? [[String: Any]] else{
fatalError("`JSON Data Serialize Failed`")
}
// 數(shù)據(jù)整理
var products = [GroceryProduct]()
for dict in array {
products.append(GroceryProduct.init(name: dict["name"] as? String ?? "",
points: dict["points"] as? Int ?? 0,
description: dict["description"] as? String ?? ""))
}
print(products)
Note:Swift編程中官方推薦用Struct代替Class笆凌,因?yàn)椴徽即鎯?chǔ)空間圣猎,但在實(shí)際開發(fā)中,如果用Struct去存儲(chǔ)解析出來的JSON數(shù)據(jù)乞而,還是比較麻煩的送悔,尤其是在JSON序列化方面。下面用Class代替Struct演示Class在JSON序列化過程中的方便之處爪模。
// 數(shù)據(jù)解析和處理全封裝在數(shù)據(jù)Model里欠啤,更體現(xiàn)封裝性
class GroceryProduct: NSObject{
var name = ""
var points = 0
var descript = ""
override func setValue(_ value: Any?, forKey key: String) {
// 攔截并進(jìn)行數(shù)據(jù)處理
if key == "points" {
points = (value as? Int) ?? 10
}
else{
super.setValue(value, forKey: key)
}
}
// 未定義key的處理
override func setValue(_ value: Any?, forUndefinedKey key: String) {
if key == "description" {
descript = value as? String ?? ""
}
}
}
// 數(shù)據(jù)整理
var products = [GroceryProduct]()
for dict in array {
let product = GroceryProduct()
product.setValuesForKeys(dict)
products.append(product)
}
print(products)
- JSON編碼
// JSON編碼
struct GroceryProduct{
var name: String
var points: Int
var description: String
// 將對(duì)象中的屬性-值轉(zhuǎn)換為JSON字典
func JSONDictionary(ignored keys: [String] = []) -> [String: Any] {
var dictionary = [String: Any]()
let mirror = Mirror.init(reflecting: self)
for (key, value) in mirror.children {
guard let key = key else{
continue
}
guard !keys.contains(key) else{
continue
}
dictionary.updateValue(value, forKey: key)
}
return dictionary
}
}
// 需要編碼的JSON Object
var jsonArray = [[String: Any]]()
for product in products {
jsonArray.append(product.JSONDictionary())
}
// 判斷是否是合法的JSON Object
guard JSONSerialization.isValidJSONObject(jsonArray) else{
fatalError("`Not Validate JSON Object`")
}
// 對(duì)象編碼為JSON Data,并解析為JSON Text
guard let data = try? JSONSerialization.data(withJSONObject: jsonArray, options: .prettyPrinted),
let jsonText = String(data: data, encoding: .utf8) else{
fatalError("`JSON Object Encode Failed`")
}
print(jsonText)
Note: 合法的JSON Object應(yīng)滿足:
- 頂層對(duì)象為
數(shù)組
或字典對(duì)象
- 數(shù)組/字典中的所有對(duì)象必須為
字符串, 數(shù)字類型NSNumber, 數(shù)組, 字典, 或 NSNull
- 所有的字典keys為
字符串
- 所有的數(shù)字對(duì)象不能為
NaN 或 infinity
所以struct / class 對(duì)象在JSON編碼過程中需要自己手動(dòng)轉(zhuǎn)換成字典/數(shù)組屋灌,才可以正確被編碼為JSON Data洁段,并轉(zhuǎn)換為字符串,然后發(fā)給服務(wù)器共郭。
Swift4.0中JSON的操作
Swift4.0中利用全新采用JSONDecoder/JSONEncoder類來實(shí)現(xiàn)JSON數(shù)據(jù)的解析和編碼祠丝。
JSONDecoder
- 要將JSON Data解析成相應(yīng)的數(shù)據(jù)模型,并匹配相應(yīng)的屬性-值除嘹,對(duì)應(yīng)的Struct或Class類型要遵守Decodable協(xié)議
//Decodable只能解析写半,不能被編碼
struct GroceryProduct: Decodable{
var name: String
var points: Int
var description: String
}
func swift4JSONParser() {
// 數(shù)據(jù)獲取
guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),
let data = try? Data.init(contentsOf: fileURL) else{
fatalError("`JSON File Fetch Failed`")
}
// 利用JSONDecoder來解析JSON Data,解析成[GroceryProduct].self數(shù)組類型
let decoder = JSONDecoder()
guard let products = try? decoder.decode([GroceryProduct].self, from: data) else{
fatalError("`JSON Decode Failed`")
}
print(products)
}
Custom Key Names
- 有些時(shí)候尉咕,服務(wù)器返回的JSON數(shù)據(jù)中的字段名采用“蛇形”命名法叠蝇,如果要轉(zhuǎn)成iOS中“駝峰”命名法,就要手動(dòng)對(duì)keys做一次匹配年缎。
- Swift4.0中悔捶,只要指定Struct/Class中的CodingKeys并遵守CodingKeys協(xié)議枚舉類型屬性,并實(shí)現(xiàn)對(duì)應(yīng)關(guān)系单芜,就可以自動(dòng)進(jìn)行匹配替換解析蜕该。但注意如果CodingKeys中case沒有匹配到JSON中的字段,解析就會(huì)失敗缓溅。
- 從這一點(diǎn)來說蛇损,還是挺麻煩的赁温,不如用Class中的
setValue(_ value: Any?, forUndefinedKey key: String)
坛怪,然后匹配指定對(duì)應(yīng)的屬性名稱。
struct GroceryProduct: Decodable{
var name: String
var points: Int
var description: String
// CodingKeys
private enum CodingKeys: String, CodingKey{
case name = "product_name"
case points = "product_points"
case description //保持一致股囊,但必須實(shí)現(xiàn)所有屬性
}
}
func swift4JSONParser() {
// 數(shù)據(jù)獲取
guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),
let data = try? Data.init(contentsOf: fileURL) else{
fatalError("`JSON File Fetch Failed`")
}
let decoder = JSONDecoder()
guard let products = try? decoder.decode([GroceryProduct].self, from: data) else{
fatalError("`JSON Decode Failed`")
}
print(products)
}
Simple Nested JSON Data
Swift4.0中對(duì)于JSON數(shù)據(jù)的嵌套結(jié)構(gòu)解析袜匿,也有了新的方式,不過還是較為簡單稚疹。
- JSON數(shù)據(jù):
[
{
"name": "Home Town Market",
"products": [
{
"name": "Banana",
"points": 200,
"description": "A banana that's perfectly ripe."
},
{
"name": "Apple",
"points": 200,
"description": "A banana that's perfectly ripe."
}
]
}
]
- 定義結(jié)構(gòu)體
struct Product: Decodable {
var name: String
var points: Int
var description: String
}
struct GroceryStore: Decodable {
var name: String
var products: [Product]
}
- 嵌套解析
func swift4JSONParser() {
// 數(shù)據(jù)獲取
guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),
let data = try? Data.init(contentsOf: fileURL) else{
fatalError("`JSON File Fetch Failed`")
}
// 會(huì)自動(dòng)匹配解析成相應(yīng)的Product對(duì)象居灯,因?yàn)镻roduct也實(shí)現(xiàn)了Decodable協(xié)議
let decoder = JSONDecoder()
guard let stores = try? decoder.decode([GroceryStore].self, from: data) else{
fatalError("`JSON Decode Failed`")
}
print(stores)
Multiple Level Nested JSON Data
多層嵌套數(shù)據(jù)解析時(shí)祭务,有一些結(jié)構(gòu)是我們不需要存儲(chǔ)的,這就我們定義一個(gè)中間的service模型來臨時(shí)搭建這個(gè)結(jié)構(gòu)怪嫌。
- JSON數(shù)據(jù):
[
{
"name": "Big City Market",
"aisles": [
{
"name": "Sale Aisle",
"shelves": [
{
"name": "Seasonal Sale",
"product": {
"name": "Chestnuts",
"points": 700,
"description": "Chestnuts that were roasted over an open fire."
}
},
{
"name": "Last Season's Clearance",
"product": {
"name": "Pumpkin Seeds",
"points": 400,
"description": "Seeds harvested from a pumpkin."
}
}
]
}
]
}
]
- 定義存儲(chǔ)數(shù)據(jù)模型
struct Product: Decodable {
var name: String
var points: Int
var description: String
}
struct GroceryStore {
var name: String
var products: [Product]
}
// 中間`架構(gòu)`類型
struct GroceryStoreService: Decodable {
let name: String
let aisles: [Aisle]
struct Aisle: Decodable {
let name: String
let shelves: [Shelf]
struct Shelf: Decodable {
let name: String
let product: Product
}
}
}
// 擴(kuò)展接口义锥,實(shí)現(xiàn)數(shù)據(jù)解析
extension GroceryStore {
init(from service: GroceryStoreService) {
name = service.name
products = []
for aisle in service.aisles {
for shelf in aisle.shelves{
products.append(shelf.product)
}
}
}
}
- 嵌套解析
func swift4JSONParser() {
// 數(shù)據(jù)獲取
guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),
let data = try? Data.init(contentsOf: fileURL) else{
fatalError("`JSON File Fetch Failed`")
}
let decoder = JSONDecoder()
guard let serviceStores = try? decoder.decode([GroceryStoreService].self, from: data)else{
fatalError("`JSON Decode Failed`")
}
// 數(shù)據(jù)剝離存儲(chǔ)
let stores = serviceStores.map{ GroceryStore(from: $0) }
print(stores)
}
Merge Data from Different Depths
合并不同深度層的數(shù)據(jù)。此時(shí)一般要轉(zhuǎn)換成KeyedDecodingContainer進(jìn)行解析岩灭。
- JSON數(shù)據(jù):
{
"Banana": {
"points": 200,
"description": "A banana grown in Ecuador."
},
"Orange": {
"points": 100,
"description": "A juicy orange."
}
}
- 數(shù)據(jù)模型:
struct GroceryStore {
struct Product {
let name: String
let points: Int
let description: String
}
var products: [Product]
init(products: [Product] = []) {
self.products = products
}
}
- 合并解析
// 擴(kuò)展增加ProductKey實(shí)現(xiàn)CodingKey拌倍,便于深層次解析屬性
extension GroceryStore {
struct ProductKey: CodingKey {
// 實(shí)現(xiàn)協(xié)議方法和屬性
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
// 自定義keys
static let points = ProductKey(stringValue: "points")!
static let description = ProductKey(stringValue: "description")!
}
}
// 擴(kuò)展實(shí)現(xiàn)Decodable協(xié)議,并通過decoder.container找到key對(duì)應(yīng)的容器對(duì)象
extension GroceryStore: Decodable{
init(from decoder: Decoder) throws {
var products = [Product]()
// 找到包含ProductKey中的屬性的所有容器
let container = try decoder.container(keyedBy: ProductKey.self)
// 然后遍歷這個(gè)容器中的所有key噪径,解析出容器中key對(duì)應(yīng)的數(shù)據(jù)值
for key in container.allKeys {
let productContainer = try container.nestedContainer(keyedBy: ProductKey.self, forKey: key)
let points = try productContainer.decode(Int.self, forKey: .points)
let description = try productContainer.decode(String.self, forKey: .description)
let product = Product(name: key.stringValue, points: points, description: description)
products.append(product)
}
self.init(products: products)
}
}
func swift4JSONParser() {
// 數(shù)據(jù)獲取
guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),
let data = try? Data.init(contentsOf: fileURL) else{
fatalError("`JSON File Fetch Failed`")
}
// 數(shù)據(jù)解析
let decoder = JSONDecoder()
guard let store = try? decoder.decode(GroceryStore.self, from: data)else{
fatalError("`JSON Decode Failed`")
}
print(store.products)
}
JSONEncoder
要實(shí)現(xiàn)包含Struct/Class對(duì)象的JSON對(duì)象的編碼柱恤,在Swift4.0中較為簡單,只需要遵守Encodable協(xié)議找爱,并指定要編碼的keys和實(shí)現(xiàn)協(xié)議encode方法即可梗顺。
- JSON數(shù)據(jù):
[
{
"name": "Vegetables Store",
"products": [
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange",
"points": 100,
"description": "A juicy orange."
}
]
}
]
- 編碼實(shí)現(xiàn)
struct Product: Decodable {
let name: String
let points: Int
let description: String
}
struct GroceryStore: Decodable {
var name: String
var products: [Product]
}
// 實(shí)現(xiàn)編碼協(xié)議
extension GroceryStore: Encodable{
private enum CodingKeys: CodingKey{
case name
case products
}
// 封裝要編碼的數(shù)據(jù)結(jié)構(gòu)
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(products, forKey: .products)
}
}
extension Product: Encodable{
private enum CodingKeys: CodingKey{
case name
case points
case description
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(points, forKey: .points)
try container.encode(description, forKey: .description)
}
}
// 要求object為實(shí)現(xiàn)了Encodable協(xié)議的對(duì)象
func swift4JSONEncode<T: Encodable> (withJSONObject object: T){
// 編碼
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
guard let encodedData = try? encoder.encode(object),
let jsonText = String(data: encodedData, encoding: .utf8) else {
fatalError("`JSON Encode Failed`")
}
print(jsonText)
}
補(bǔ)充2017-09-22
在有些情況下,需要struct對(duì)象中的某些屬性不是全部需要被存儲(chǔ)和解析车摄,就需要手動(dòng)進(jìn)行decode了
- 定義結(jié)構(gòu)體
struct GitHubUser {
var id: Int
var type: String
var loginName: String
var avatarUrl: String
var homepageUrl: String
var profileUrl: String
var name: String
var company: String
var location: String
var blog: String
var bio: String
enum CodingKeys: String, CodingKey{
case id,type,name,company,location,blog,bio
case loginName = "login"
case avatarUrl = "avatar_url"
case homepageUrl = "html_url"
case profileUrl = "url"
}
}
- 自定義解析
extension GitHubUser: Decodable{
// 必須實(shí)現(xiàn)所有屬性 - 初始值
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int.self, forKey: .id)
type = try values.decode(String.self, forKey: .type)
loginName = try values.decode(String.self, forKey: .loginName)
avatarUrl = try values.decode(String.self, forKey: .avatarUrl)
homepageUrl = try values.decode(String.self, forKey: .homepageUrl)
profileUrl = try values.decode(String.self, forKey: .profileUrl)
// 以下屬性為可選解析的寺谤,設(shè)置默認(rèn)值
do {
name = try values.decode(String.self, forKey: .name)
}catch{
name = ""
}
do {
company = try values.decode(String.self, forKey: .company)
}catch{
company = ""
}
do {
location = try values.decode(String.self, forKey: .location)
}catch{
location = ""
}
do{
blog = try values.decode(String.self, forKey: .blog)
}catch{
blog = ""
}
do{
bio = try values.decode(String.self, forKey: .bio)
}catch{
bio = ""
}
}
}
如果對(duì)你有幫助,別忘了點(diǎn)個(gè)??并關(guān)注下我哦吮播。