swift php json解析,Swift 4.0 | JSON数据的解析和编码

文 / 菲拉兔

d7ba741e6009

自己撸的图

要求:

Platform: iOS8.0+

Language: Swift4.0

Editor: Xcode9

【问题补充2017-09-28】

最近我发现了一个问题:在Swift4.0中对JSON数据进行解析的时候,如果还用老的JSONSerialization类的话,会出现一个BUG:

问题: 比如我有一个NSObject的类叫Student,其中包含一个var name = ""属性,那么在以上方法解析JSON数据的过程中,name的值将不被写入,这应该是Swift4.0的一个BUG;

解决方法:

用其他的名字替代name字段(暂时发现只有对这个属性不起作用),例如var sname = "";

用JSONDecoder新的方式去解析;

Swift4.0以前和OC时代的JSON数据处理

Swift(1..<4)& Objective-C

Swift4.0以前的JSON解析/编码,和OC时代一样,都是通过NSJSONSerialization类的一些类方法进行处理的

JSON解析

struct GroceryProduct{

var name: String

var points: Int

var description: String

}

// 数据获取

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`")

}

// 数据整理

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,因为不占存储空间,但在实际开发中,如果用Struct去存储解析出来的JSON数据,还是比较麻烦的,尤其是在JSON序列化方面。下面用Class代替Struct演示Class在JSON序列化过程中的方便之处。

// 数据解析和处理全封装在数据Model里,更体现封装性

class GroceryProduct: NSObject{

var name = ""

var points = 0

var descript = ""

override func setValue(_ value: Any?, forKey key: String) {

// 拦截并进行数据处理

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 ?? ""

}

}

}

// 数据整理

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

// 将对象中的属性-值转换为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`")

}

// 对象编码为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应满足:

顶层对象为数组或字典对象

数组/字典中的所有对象必须为字符串, 数字类型NSNumber, 数组, 字典, 或 NSNull

所有的字典keys为字符串

所有的数字对象不能为NaN 或 infinity

所以struct / class 对象在JSON编码过程中需要自己手动转换成字典/数组,才可以正确被编码为JSON Data,并转换为字符串,然后发给服务器。

Swift4.0中JSON的操作

Swift4.0中利用全新采用JSONDecoder/JSONEncoder类来实现JSON数据的解析和编码。

JSONDecoder

要将JSON Data解析成相应的数据模型,并匹配相应的属性-值,对应的Struct或Class类型要遵守Decodable协议

//Decodable只能解析,不能被编码

struct GroceryProduct: Decodable{

var name: String

var points: Int

var description: String

}

func swift4JSONParser() {

// 数据获取

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数组类型

let decoder = JSONDecoder()

guard let products = try? decoder.decode([GroceryProduct].self, from: data) else{

fatalError("`JSON Decode Failed`")

}

print(products)

}

Custom Key Names

有些时候,服务器返回的JSON数据中的字段名采用“蛇形”命名法,如果要转成iOS中“驼峰”命名法,就要手动对keys做一次匹配。

Swift4.0中,只要指定Struct/Class中的CodingKeys并遵守CodingKeys协议枚举类型属性,并实现对应关系,就可以自动进行匹配替换解析。但注意如果CodingKeys中case没有匹配到JSON中的字段,解析就会失败。

从这一点来说,还是挺麻烦的,不如用Class中的setValue(_ value: Any?, forUndefinedKey key: String),然后匹配指定对应的属性名称。

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 //保持一致,但必须实现所有属性

}

}

func swift4JSONParser() {

// 数据获取

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中对于JSON数据的嵌套结构解析,也有了新的方式,不过还是较为简单。

JSON数据:

[

{

"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."

}

]

}

]

定义结构体

struct Product: Decodable {

var name: String

var points: Int

var description: String

}

struct GroceryStore: Decodable {

var name: String

var products: [Product]

}

嵌套解析

func swift4JSONParser() {

// 数据获取

guard let fileURL = Bundle.main.url(forResource: "test.json", withExtension: nil),

let data = try? Data.init(contentsOf: fileURL) else{

fatalError("`JSON File Fetch Failed`")

}

// 会自动匹配解析成相应的Product对象,因为Product也实现了Decodable协议

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

多层嵌套数据解析时,有一些结构是我们不需要存储的,这就我们定义一个中间的service模型来临时搭建这个结构。

JSON数据:

[

{

"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."

}

}

]

}

]

}

]

定义存储数据模型

struct Product: Decodable {

var name: String

var points: Int

var description: String

}

struct GroceryStore {

var name: String

var products: [Product]

}

// 中间`架构`类型

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

}

}

}

// 扩展接口,实现数据解析

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() {

// 数据获取

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`")

}

// 数据剥离存储

let stores = serviceStores.map{ GroceryStore(from: $0) }

print(stores)

}

Merge Data from Different Depths

合并不同深度层的数据。此时一般要转换成KeyedDecodingContainer进行解析。

JSON数据:

{

"Banana": {

"points": 200,

"description": "A banana grown in Ecuador."

},

"Orange": {

"points": 100,

"description": "A juicy orange."

}

}

数据模型:

struct GroceryStore {

struct Product {

let name: String

let points: Int

let description: String

}

var products: [Product]

init(products: [Product] = []) {

self.products = products

}

}

合并解析

// 扩展增加ProductKey实现CodingKey,便于深层次解析属性

extension GroceryStore {

struct ProductKey: CodingKey {

// 实现协议方法和属性

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")!

}

}

// 扩展实现Decodable协议,并通过decoder.container找到key对应的容器对象

extension GroceryStore: Decodable{

init(from decoder: Decoder) throws {

var products = [Product]()

// 找到包含ProductKey中的属性的所有容器

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

// 然后遍历这个容器中的所有key,解析出容器中key对应的数据值

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() {

// 数据获取

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 store = try? decoder.decode(GroceryStore.self, from: data)else{

fatalError("`JSON Decode Failed`")

}

print(store.products)

}

JSONEncoder

要实现包含Struct/Class对象的JSON对象的编码,在Swift4.0中较为简单,只需要遵守Encodable协议,并指定要编码的keys和实现协议encode方法即可。

JSON数据:

[

{

"name": "Vegetables Store",

"products": [

{

"name": "Banana",

"points": 200,

"description": "A banana grown in Ecuador."

},

{

"name": "Orange",

"points": 100,

"description": "A juicy orange."

}

]

}

]

编码实现

struct Product: Decodable {

let name: String

let points: Int

let description: String

}

struct GroceryStore: Decodable {

var name: String

var products: [Product]

}

// 实现编码协议

extension GroceryStore: Encodable{

private enum CodingKeys: CodingKey{

case name

case products

}

// 封装要编码的数据结构

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为实现了Encodable协议的对象

func swift4JSONEncode (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)

}

补充2017-09-22

在有些情况下,需要struct对象中的某些属性不是全部需要被存储和解析,就需要手动进行decode了

定义结构体

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{

// 必须实现所有属性 - 初始值

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)

// 以下属性为可选解析的,设置默认值

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 = ""

}

}

}

如果对你有帮助,别忘了点个❤️并关注下我哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值