Elevate 是一个 JSON 解析框架,它利用 Swift 使解析变得简单、可靠且可组合。
Elevate 不应再用于新的功能开发。我们建议您使用 Apple 在
Foundation
框架中提供的Codable
协议来替代它。在可预见的未来,我们将继续支持和更新 Elevate。
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 是一个去中心化的依赖管理器,它可以构建您的依赖项并为您提供二进制框架。
您可以使用 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 对象。已向集合类型添加了便捷的扩展,以便于在一次传递中编码嵌套对象。
在您使您的模型对象成为 Decodable
或为它们实现 Decoder
之后,使用 Elevate 进行解析就像这样简单
let avatar: Avatar = try Elevate.decodeObject(from: data, atKeyPath: "response.avatar")
如果您的对象或数组位于根级别,请将空字符串传递到
atKeyPath
中。
在前面的示例中,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
定义。Decoder
中以进行进一步操作。请参阅上面示例中的 birthDate
属性。DateDecoder
是 Elevate 提供的标准 Decoder
,使日期解析变得轻松自如。Decoder
或 Decodable
类型提供给 .Array
类型的属性,以将数组中的每个项目解析为该类型。这也适用于 .Dictionary
类型以解析嵌套的 JSON 对象。entity
字典中自动提取 Any
值并将其强制转换为返回类型。Elevate 包含四个属性提取运算符,以便于从 entity
字典中提取值并将 Any
值强制转换为适当的类型。
<-!
- 从 entity
字典中提取指定键的值。此运算符应仅用于非可选属性。<-?
- 从 entity
字典中提取指定键的可选值。此运算符应仅用于可选属性。<--!
- 从 entity
字典中提取指定键的数组作为指定的数组类型。此运算符应仅用于非可选数组属性。<--?
- 从 entity
字典中提取指定键的数组作为指定的可选数组类型。扩展模型对象以符合 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 字典非常简单。通过调用数组上的 json
属性,也可以轻松地将 Address
对象数组转换为 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
)
}
}
然后将两个不同的 Decoder
对象与 Parser
一起使用
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()
)
每个 Decoder
都被设计为处理不同的 JSON 结构以创建 Avatar
。每个都使用特定于其处理的 JSON 数据的键路径,然后将这些路径映射回 Avatar
对象上的属性。这是一个非常简单的示例,仅用于演示目的。还有许多更复杂的示例可以通过 Decoder
协议以类似的方式处理。
Decoder
协议的第二个用途是允许进一步操作属性的值。最常见的示例是日期字符串。以下是 DateDecoder
如何实现 Decoder
协议
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 中,以方便您使用。