提升 4.0.0

提升 4.0.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最后发布2019年4月
SPM支持SPM

Christian Noon,《Ben Scheirman》维护。



提升 4.0.0

  • Eric Appel 和 Christian Noon

提升

Build Status CocoaPods Compatible Carthage Compatible Platform

提升是一个利用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 解析和验证既简单又强大。这是通过一系列协议和类实现的,可以用来创建 DecodableDecoder 类。通过使用 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 数据初始化的非可选常量的完整初始化结构体。

在此示例中,还有一些值得注意的事情

  1. 作为扩展实现了对 Decodable 协议的遵从。这使得结构体可以保留其自动成员初始化器。
  2. 支持标准原始类型以及 URLArrayDictionary 类型。有关完整列表,请参阅 ParserPropertyProtocol 的定义。
  3. Elevate 便于将解析的属性传递给 Decoder 进行进一步处理。请参阅上面的示例中的 birthDate 属性。Elevate 提供了一个标准 Decoder DateDecoder,用于简化日期解析。
  4. 可以将 DecoderDecodable 类型提供给 .Array 类型的属性,以解析数组中的每个项目到该类型。这也适用于 .Dictionary 类型,以解析嵌套 JSON 对象。
  5. 解析器保证属性将是指定的类型。因此,可以安全地使用自定义运算符从 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。在 ArraySetDictionary 上的集合类型扩展使得将具有多个 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)
}

您可以在解析期间创建任何所需的解码器,并使用它们来处理您的属性。其他用途可能包括创建一个 StringToBoolDecoderStringToFloatDecoder,这些解码器可以解析从 JSON 字符串值中获取的 BoolFloatDateDecoderStringToIntDecoder 已包含在 Elevate 中,供您方便使用。


创作者