Swift 使用JSON数据结构

如果你的APP从服务器获取到的数据格式为JSON。你可以使用JSONSerialization把JSON解析成Swift的数据类型,比如Dictionary,Array,String,Number,Bool。不过,因为你的APP不能直接使用JSON的结构,可以将它解析成模型对象。本文描述了一些方法可以让你的APP使用JSON数据。

从JSON中取值

JSONSerialization中有个方法jsonObject(with:options:)可以返回一个类型为Any的数据,并且如果data数据不能被解析会返回错误信息

import Foundation

let data: Data // 从网络请求下来的数据
let json = try? JSONSerialization.jsonObject(with: data, options: [])

复制代码

尽管JSON数据中只包含一个值,每次请求都会返回一个对象或者数组。可以使用optional类型使用as?,在if或者guard条件语句中提取出自定义的数据。从JSON中提取Dictionary数据需要元素条件是[String: Any]。提取Array类型数据需要的元素条件是[Any](或者更加明确的数组元素类型,比如[Strng])。可以使用字典的键值或者数组的下标配合元素的类型来获取对应的数据。

// JSON中对象
/*
	{
		"someKey": 42.0,
		"anotherKey": {
			"someNestedKey": true
		}
	}
*/
if let dictionary = jsonWithObjectRoot as? [String: Any] {
	if let number = dictionary["someKey"] as? Double {
		// access individual value in dictionary
	}

	for (key, value) in dictionary {
		// access all key / value pairs in dictionary
	}

	if let nestedDictionary = dictionary["anotherKey"] as? [String: Any] {
		// access nested dictionary values by key
	}
}

// JSON中是数组
/*
	[
		"hello", 3, true
	]
*/
if let array = jsonWithArrayRoot as? [Any] {
	if let firstObject = array.first {
		// access individual object in array
	}

	for object in array {
		// access all objects in array
	}

	for case let string as String in array {
		// access only string values in array
	}
}
复制代码

Swift包含基础API可以安全、快速的提取和处理JSON数据。

根据JSON创建模型对象

大多数APP遵循MVC设计模式,通常将JSON转成APP中指定的模型对象

例如,编写一个为搜索当地参观的APP,你可能需要创建一个接收JSON数据并初始化为餐厅模型的方法,用一个异步网络请求,然后返回一个包含餐厅对象的数组。

例如下面的餐厅模型:

import Foundation

struct Restaurant {
	enum Meal: String {
		case breakfast, lunch, dinner
	}

	let name: String
	let location: (latitude: Double, longitude: Double)
	let meals: Set<Meal>
}
复制代码

餐厅有一个String类型的名字,一个位置坐标,和一个包含进餐类型枚举值

下面可能是一个服务器返回的餐厅数据:

{
	"name": "Caffè Macs",
	"coordinates": {
		"lat": 37.330576,
		"lng": -122.029739
	},
	"meals": ["breakfast", "lunch", "dinner"]
}
复制代码

写一个JSON的可选型初始值

从JSON中初始化一个餐厅对象,将JSON数据提取和转换成一个Any类型的对象

extension Restaurant {
	init?(json: [String: Any]) {
		guard let name = json["name"] as? String,
			let coordinatesJSON = json["coordinates"] as? [String: Double],
			let latitude = coordinatesJSON["lat"],
			let longitude = coordinatesJSON["lng"],
			let mealsJSON = json["meals"] as? [String]
		else {
			return nil
		}

		var meals: Set<Meal> = []
		for string in mealsJSON {
			guard let meal = Meal(rawValue: string) else {
				return nil
			}

			meals.insert(meal)
		}

		self.name = name
		self.coordinates = (latitude, longitude)
		self.meals = meals
	}
}
复制代码

如果你的APP服务器不会只返回给一种模型对象,应该考虑实现不同的初始化方法处理每一种可能的类型。 在上面的示例中,提取到的每个常量的值是通过JSON使用可选值指定为字典。 进餐名称提取的数据只能作为初始值。经度纬度可以组合成一个元组。进餐的类型可以使用枚举值来表示。

处理JSON初始化错误

上面的示例实现了一个可选型的初始化,如果失败,则返回nil。或者你可以定义一个符合协议的类型来表示初始化错误,当反序列化失败的时候抛出一个错误类型。

enum SerializationError: Error {
	case missing(String)
	case invalid(String, Any)
}

extension Restaurant {
	init(json: [String: Any]) throws {
		// Extract name
		guard let name = json["name"] as? String else {
			throw SerializationError.missing("name")
		}

		// Extract and validate coordinates
		guard let coordinatesJSON = json["coordinates"] as? [String: Double],
			let latitude = coordinatesJSON["lat"],
			let longitude = coordinatesJSON["lng"]
		else {
			throw SerializationError.missing("coordinates")
		}

		let coordinates = (latitude, longitude)
		guard case (-90...90, -180...180) = coordinates else {
			throw SerializationError.invalid("coordinates", coordinates)
		}

		// Extract and validate meals
		guard let mealsJSON = json["meals"] as? [String] else {
			throw SerializationError.missing("meals")
		}

		var meals: Set<Meal> = []
		for string in mealsJSON {
			guard let meal = Meal(rawValue: string) else {
				throw SerializationError.invalid("meals", string)
			}

			meals.insert(meal)
		}

		// Initialize properties
		self.name = name
		self.coordinates = coordinates
		self.meals = meals
	}
}
复制代码

在这里,餐厅类型嵌套的声明一个SerializationError的枚举类型,定义枚举值的属性为相关值缺失或者相关值无效。在这一版本的JSON初始化中,不再是通过返回nil表示失败,而是返回一个具体的错误原因。这一版本中还加入了对数据的验证(确保坐标值代表一个有效的地理坐标,每个进餐名字对应指定的枚举值)。

获取结果的方法

服务器通常在一个JSON中返回多个结果。例如,搜索服务可能返回包含0个或者多个餐厅及匹配的请求参数,还包括其他的一些数据:

{
	"query": "sandwich",
	"results_count": 12,
	"page": 1,
	"results": [
		{
			"name": "Caffè Macs",
			"coordinates": {
				"lat": 37.330576,
				"lng": -122.029739
			},
			"meals": ["breakfast", "lunch", "dinner"]
		},
		...
	]
}
复制代码

你可以创建一个类方法,将一个相应的餐厅对象转换成查询方法的参数,通过http请求发送给服务器。这段代码同时负责响应处理服务器返回的JSON数据,异步处理JSON中返回的数组结果,反序列化成餐厅对象

extension Restaurant {
	private let urlComponents: URLComponents // base URL components of the web service
	private let session: URLSession // shared session for interacting with the web service

	static func restaurants(matching query: String, completion: ([Restaurant]) -> Void) {
		var searchURLComponents = urlComponents
		searchURLComponents.path = "/search"
		searchURLComponents.queryItems = [URLQueryItem(name: "q", value: query)]
		let searchURL = searchURLComponents.url!

		session.dataTask(url: searchURL, completion: { (_, _, data, _)
			var restaurants: [Restaurant] = []

			if let data = data,
				let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
				for case let result in json["results"] {
					if let restaurant = Restaurant(json: result) {
						restaurants.append(restaurant)
					}
				}
			}

			completion(restaurants)
		}).resume()
	}
}
复制代码

当用户在搜索栏输入文本的时候,视图控制器调用这个方法对餐厅进行匹配:

import UIKit

extension ViewController: UISearchResultsUpdating {
	func updateSearchResultsForSearchController(_ searchController: UISearchController) {
		if let query = searchController.searchBar.text, !query.isEmpty {
			Restaurant.restaurants(matching: query) { restaurants in
				self.restaurants = restaurants
				self.tableView.reloadData()
			}
		}
	}
}
复制代码

即便是网络服务的细节发生变化,视图控制器只是以用分离封装的接口方法访问餐厅数据,与网络服务部分降低耦合

参考苹果的关于Swift使用JSON的官方博客

转载于:https://juejin.im/post/5a93c95051882524d842e571

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值