提升
提升是一个利用Swift使解析变得简单、可靠和可组合的JSON解析框架。
提升不再应该用于新功能开发。我们建议使用Apple在
Foundation
框架中提供的Codable
协议来替代。我们将继续支持并更新提升。
功能
- 验证完整的JSON负载
- 将复杂JSON解析为强类型对象
- 支持可选和必填值
- 方便灵活的协议用于定义对象解析
- 可以将大型对象图解析为其组成部分
- 在整个对象图中汇总错误
要求
- iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
- Xcode 9.3+
- Swift 4.2+
通讯
- 需要帮助吗?请提交一个issue。
- 有功能请求?请提交一个issue。
- 发现了一个 bug?请创建一个 issue。
- 想要贡献代码?Fork 仓库并提交一个 pull request。
安装
CocoaPods
CocoaPods 是 Cocoa 项目的依赖管理器。您可以使用以下命令进行安装
[sudo] gem install cocoapods
需要 CocoaPods 1.3+ 版本。
要使用 CocoaPods 将 Elevate 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '11.0'
use_frameworks!
pod 'Elevate', '~> 3.0'
Carthage
Carthage 是一个去中心化的依赖管理器,它会构建您的依赖并提供二进制框架。
您可以使用以下命令使用 Homebrew 安装 Carthage
brew update
brew install carthage
要使用 Carthage 将 Elevate 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它
github "Nike-Inc/Elevate" ~> 3.0
要在 iOS 上仅构建 Elevate,使用以下 Carthage 命令
carthage update --platform iOS
使用方法
Elevate 的目标是使 JSON 解析和验证既简单又强大。这是通过一系列协议和类实现的,可以用来创建 Decodable
和 Decoder
类。通过使用 Elevate 的解析基础设施,您可以将 JSON 数据轻松解析为强类型模型对象或简单字典,只需指定每个属性键路径及其关联的类型。Elevate 将验证键是否存在(如果不是可选的)以及它们是否是正确的类型。验证错误将随着 JSON 数据的解析而汇总。如果遇到错误,将抛出 ParserError
。
Elevate 还支持使用轻量级的 Encodable
协议将模型对象编码回 JSON 对象。已向集合类型添加方便的扩展,以便可以单次行程中轻松编码嵌套对象。
使用 Elevate 解析 JSON
在使您的模型对象成为 Decodable
或为它们实现 Decoder
后,使用 Elevate 解析非常简单
let avatar: Avatar = try Elevate.decodeObject(from: data, atKeyPath: "response.avatar")
如果您的对象或数组位于根级别,请将空字符串传入
atKeyPath
。
创建 Decodable
在上一个例子中,Avatar
实现了 Decodable
协议。通过在对象上实现 Decodable
协议,它可以用 Elevate 从 JSON 数据解析顶级对象、子对象甚至头像对象的数组。
public protocol Decodable {
init(json: Any) throws
}
json: Any
通常是从 JSONSerialization
API 创建的 [String: Any]
实例。使用 Elevate 的 Parser.parseEntity
方法来定义要验证的 JSON 数据的结构并执行解析。
struct Person {
let identifier: String
let name: String
let nickname: String?
let birthDate: Date
let isMember: Bool?
let addresses: [Address]
}
extension Person: Elevate.Decodable {
fileprivate struct KeyPath {
static let id = "identifier"
static let name = "name"
static let nickname = "nickname"
static let birthDate = "birthDate"
static let isMember = "isMember"
static let addresses = "addresses"
}
init(json: Any) throws {
let dateDecoder = DateDecoder(dateFormatString: "yyyy-MM-dd")
let entity = try Parser.parseEntity(json: json) { schema in
schema.addProperty(keyPath: KeyPath.id, type: .int)
schema.addProperty(keyPath: KeyPath.name, type: .string)
schema.addProperty(keyPath: KeyPath.nickname, type: .string, optional: true)
schema.addProperty(keyPath: KeyPath.birthDate, type: .string, decoder: dateDecoder)
schema.addProperty(keyPath: KeyPath.isMember, type: .bool, optional: true)
schema.addProperty(keyPath: KeyPath.addresses, type: .array, decodableType: Address.self)
}
self.identifier = entity <-! KeyPath.id
self.name = entity <-! KeyPath.name
self.nickname = entity <-? KeyPath.nickname
self.birthDate = entity <-! KeyPath.birthDate
self.isMember = entity <-? KeyPath.isMember
self.addresses = entity <--! KeyPath.addresses
}
}
以这种方式实现 Decodable
协议允许您创建包含从 JSON 数据初始化的非可选常量的完整初始化结构体。
在此示例中,还有一些值得注意的事情
- 作为扩展实现了对
Decodable
协议的遵从。这使得结构体可以保留其自动成员初始化器。 - 支持标准原始类型以及
URL
、Array
和Dictionary
类型。有关完整列表,请参阅ParserPropertyProtocol
的定义。 - Elevate 便于将解析的属性传递给
Decoder
进行进一步处理。请参阅上面的示例中的birthDate
属性。Elevate 提供了一个标准Decoder
DateDecoder
,用于简化日期解析。 - 可以将
Decoder
或Decodable
类型提供给.Array
类型的属性,以解析数组中的每个项目到该类型。这也适用于.Dictionary
类型,以解析嵌套 JSON 对象。 - 解析器保证属性将是指定的类型。因此,可以安全地使用自定义运算符从
entity
字典自动提取Any
值并将其强制转换为返回类型。
属性提取运算符
Elevate 包含四个属性提取运算符,以简化从 entity
字典中提取值并将 Any
值转换为适当类型的过程。
<-!
- 从entity
字典中提取指定键的值。此运算符仅应用于非可选属性。<-?
- 从entity
字典中提取指定键的可选值。此运算符仅应用于可选属性。<--!
- 从指定键的entity
字典中提取指定类型的数组。该运算符仅应用于非可选数组属性。<--?
- 从指定键的entity
字典中提取指定可选类型的数组。
创建 Encodable 对象
将模型对象扩展以符合 Encodable
协议比使其成为 Decodable
简单得多。由于你的对象已经是强类型的,它只需要转换为 JSON 优化的 Any
对象。基于上一个 Person
类型,让我们使其符合 Encodable
协议。
extension Person: Elevate.Encodable {
var json: Any {
var json: [String: Any] = [
KeyPath.id: identifier,
KeyPath.name: name,
KeyPath.birthDate: birthDate,
KeyPath.addresses: addresses.json
]
if let nickname = nickname { json[KeyPath.nickname] = nickname }
if let isMember = isMember { json[KeyPath.isMember] = isMember }
return json
}
}
如示例所示,将 Person
转换为 JSON 字典很简单。将 Address
对象数组转换为 JSON 也很容易,只需要对数组调用 json
属性。这之所以可行,是因为 Address
也符合 Encodable
。在 Array
、Set
和 Dictionary
上的集合类型扩展使得将具有多个 Encodable
对象层的复杂对象转换为 JSON 对象变得容易。
高级用法
解码器
在大多数情况下,实现一个 Decodable
模型对象就足以使用 Elevate 解析 JSON。但是,在某些情况下,您可能需要更多的灵活性来解析 JSON。这就是 Decoder
协议发挥作用的地方。
public protocol Decoder {
func decode(_ object: Any) throws -> Any
}
Decoder
通常实现为一个单独的对象,它返回所需模型对象的实例。这对于具有多个 JSON 映射的单个模型对象或您正在聚合多个 JSON 负载的数据时非常有用。例如,如果有两个独立的服务返回具有略微不同属性结构的 Avatar
对象的 JSON,则可以为每个映射创建一个 Decoder
来单独处理它们。
输入类型和输出类型有意保持模糊,以便提供灵活性。`Decoder` 可以返回任何你想要的数据类型 - 强类型模型对象、字典等。如果需要,它甚至可以在运行时动态返回不同的类型。
使用多个解码器
class AvatarDecoder: Elevate.Decoder {
func decode(_ object: Any) throws -> Any {
let urlKeyPath = "url"
let widthKeyPath = "width"
let heightKeyPath = "height"
let entity = try Parser.parseEntity(json: object) { schema in
schema.addProperty(keyPath: urlKeyPath, type: .url)
schema.addProperty(keyPath: widthKeyPath, type: .int)
schema.addProperty(keyPath: heightKeyPath, type: .int)
}
return Avatar(
URL: entity <-! urlKeyPath,
width: entity <-! widthKeyPath,
height: entity <-! heightKeyPath
)
}
}
class AlternateAvatarDecoder: Elevate.Decoder {
func decode(_ object: Any) throws -> Any {
let locationKeyPath = "location"
let wKeyPath = "w"
let hKeyPath = "h"
let entity = try Parser.parseEntity(json: object) { schema in
schema.addProperty(keyPath: locationKeyPath, type: .url)
schema.addProperty(keyPath: wKeyPath, type: .int)
schema.addProperty(keyPath: hKeyPath, type: .int)
}
return Avatar(
URL: entity <-! locationKeyPath,
width: entity <-! wKeyPath,
height: entity <-! hKeyPath
)
}
}
然后使用两个不同的 解码器
对象与 解析器
let avatar1: Avatar = try Elevate.decodeObject(
from: data1,
atKeyPath: "response.avatar",
with: AvatarDecoder()
)
let avatar2: Avatar = try Elevate.decodeObject(
from: data2,
atKeyPath: "alternative.response.avatar",
with: AlternateAvatarDecoder()
)
每个 解码器
都是设计来处理不同的 JSON 结构以创建一个 头像
。每个都使用特定于正在处理的 JSON 数据的关键路径,然后将这些映射回 头像
对象上的属性。这是一个为了演示目的的非常简单的例子。还有许多更复杂的例子可以通过 解码器
协议以类似的方式处理。
解码器作为属性值转换器
解码器
协议的第二种用途是允许进一步操作属性的值。最常见的例子是日期字符串。以下是如何实现 DateDecoder
的 解码器
协议
public func decode(_ object: Any) throws -> Any {
if let string = object as? String {
return try dateFromString(string, withFormatter:self.dateFormatter)
} else {
let description = "DateParser object to parse was not a String."
throw ParserError.Validation(failureReason: description)
}
}
以及它是如何解析 JSON 日期字符串的
let dateDecoder = DateDecoder(dateFormatString: "yyyy-MM-dd 'at' HH:mm")
let entity = try Parser.parseEntity(data: data) { schema in
schema.addProperty(keyPath: "dateString", type: .string, decoder: dateDecoder)
}
您可以在解析期间创建任何所需的解码器,并使用它们来处理您的属性。其他用途可能包括创建一个 StringToBoolDecoder
或 StringToFloatDecoder
,这些解码器可以解析从 JSON 字符串值中获取的 Bool
或 Float
。DateDecoder
和 StringToIntDecoder
已包含在 Elevate 中,供您方便使用。