Elevate

Build Status CocoaPods Compatible Carthage Compatible Platform

Elevate 是一个 JSON 解析框架,它利用 Swift 使解析变得简单、可靠且可组合。

Elevate 不应再用于新的功能开发。我们建议您使用 Apple 在 Foundation 框架中提供的 Codable 协议来替代它。在可预见的未来,我们将继续支持和更新 Elevate。

特性

要求

沟通

安装

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 中。

创建 Decodables

在前面的示例中,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 属性。DateDecoder 是 Elevate 提供的标准 Decoder,使日期解析变得轻松自如。
  4. 可以将 DecoderDecodable 类型提供给 .Array 类型的属性,以将数组中的每个项目解析为该类型。这也适用于 .Dictionary 类型以解析嵌套的 JSON 对象。
  5. 解析器保证属性将是指定的类型。因此,可以安全地使用自定义运算符从 entity 字典中自动提取 Any 值并将其强制转换为返回类型。

属性提取运算符

Elevate 包含四个属性提取运算符,以便于从 entity 字典中提取值并将 Any 值强制转换为适当的类型。

创建 Encodables

扩展模型对象以符合 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 也符合 EncodableArraySetDictionary 上的集合类型扩展使得将具有多层 Encodable 对象的复杂对象转换为 JSON 对象变得容易。


高级用法

Decoders

在大多数情况下,实现 Decodable 模型对象是使用 Elevate 解析 JSON 所需的全部内容。但在某些情况下,您将需要在 JSON 解析方式方面具有更大的灵活性。这就是 Decoder 协议的用武之地。

public protocol Decoder {
    func decode(_ object: Any) throws -> Any
}

Decoder 通常实现为单独的对象,该对象返回所需模型对象的实例。当您为单个模型对象具有多个 JSON 映射,或者如果您正在跨多个 JSON 负载聚合数据时,这非常有用。例如,如果有两个单独的服务为 Avatar 对象返回 JSON,这些对象具有略微不同的属性结构,则可以为每个映射创建一个 Decoder 以单独处理它们。

输入类型和输出类型被有意地模糊化,以允许灵活性。Decoder 可以返回您想要的任何类型 - 强类型模型对象、字典等。如果需要,它甚至可以在运行时动态返回不同的类型。

使用多个 Decoders

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 协议以类似的方式处理。

Decoders 作为属性值转换器

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)
}

您可以自由创建任何您喜欢的解码器,并在解析期间将它们与您的属性一起使用。其他一些用途是创建 StringToBoolDecoderStringToFloatDecoder,它们从 JSON 字符串值解析 BoolFloatDateDecoderStringToIntDecoder 已包含在 Elevate 中,以方便您使用。


创建者