JSONAPIModel 1.1.0

JSONAPIModel 1.1.0

Fabien LegoupillotBecky ChristensenGreg LeeRobin Goos 维护。



JSONAPIModel

CircleCI

简单的 JSONAPI 解析/序列化和数据模型

概览

JSONAPIModel 是一个用 Swift 编写的简单 JSONAPI 解析/序列化和数据存储库。我们从头开始构建它以满足 Envoy 的 iOS 项目需求。当我们尝试构建这个项目时,我们先考虑社区,看看是否有现成的东西。然而,我们没有找到任何适合我们需求的东西。JSONAPI 是一个非常强大的 API 架构,允许按需加载对象之间的关系。通常我们找到的实现提供了为 JSONAPI 提供的先进功能,而我们不需要这些功能。我们需要的只是一个非常简单的解析器和将数据存储到简单的 Swift 数据结构中,理想中的模型应该是这样的

class DeviceConfig {
    var id: String
    var userEmail: String
    var buttonColor: String
    // ...
}

最后,因为我们没有找到任何现成的解决方案,我们就决定自己构建,同时考虑以下一些设计目标

  • 不向数据模型的最终用户暴露任何 JSON API 相关的功能
  • 保持其简单性,仅作为简单的解析器和序列化器工作
  • 应该足够健壮
  • 易于使用

如果您期望这个项目作为一个功能全面的 JSONAPI 数据模型库,您找错地方了。

示例

正如我们在设计目标中所说的,我们希望尽可能少地暴露有关 JSONAPI 的信息,因此出于设计,要为 JSONAPIModel 创建一个模型对象相当简单。只需创建一个从 NSObject 继承的类,并使所有属性装饰有 @objc

import Foundation

@objcMembers final class Location: NSObject {
    /// ID of location
    let id: String

    /// Name of location
    var name: String!

    /// Employees in this location
    var employees: [Employee] = []

    init(id: String) {
        self.id = id
        super.init()
    }
}

另外,请注意,您需要一个带有 init(id: String) 签名的构造函数。除此之外的所有属性都应该有它们自己的默认值(这就是我们在这里对 name 使用隐式解包可选的原因,并为 employees 赋予初始值),这意味着您只需提供 id 参数就可以创建这个 JSONAPIModel 对象。

Location(id: "loc-12345")

接下来,用 JSONAPIModelType 扩展模型类。

// MARK: JSONAPIModelType
extension Location: JSONAPIModelType {
    func mapping(_ map: JSONAPIMap) throws {
        try name <- map.attribute("name")
    }

    static var metadata: JSONAPIMetadata {
        let helper = MetadataHelper<Location>(type: "locations")
        helper.hasMany("employees", { $0.employees }, { $0.employees = $1 })
        return helper.metadata
    }
}

mapping(_ map: JSONAPIMap) 函数中,您可以像这样使用 <- 级联操作符绑定属性:

try name <- map.attribute("name")

对于关系和 JSON API 模型 type,您需要定义如下的 static var metadata: JSONAPIMetadata

static var metadata: JSONAPIMetadata {
    let helper = MetadataHelper<Location>(type: "locations")
    helper.hasMany("employees", { $0.employees }, { $0.employees = $1 })
    return helper.metadata
}

这里的 MetadataHelper< Location > (type: "locations")Location 模型创建了一个元数据助手,其中 locations 是 JSON API 模型 type

接下来,您可以使用 helper.hasManyhelper.hasOne 来处理一对一和多对一关系。第一个参数是 relationships 字典中的键。第二个参数是用于获取员工值的获取器。第三个参数是将值分配给员工属性(在我们的例子中,一个 Employee 数组将作为 $1 提供)的设置器。

最后,从助手返回元数据。

同样,您可以按照这种方式定义另一个模型 Employee

import Foundation

/// Employee info
final class Employee: NSObject {
    /// ID of Employee
    let id: String
    /// Name of employee
    @objc var name: String!

    init(id: String) {
        self.id = id
        super.init()
    }
}

// MARK: JSONAPIModelType
extension Employee: JSONAPIModelType {
    func mapping(_ map: JSONAPIMap) throws {
        try name <- map.attribute("name")
    }

    static var metadata: JSONAPIMetadata {
        let helper = MetadataHelper<Employee>(type: "employees")
        return helper.metadata
    }
}

现在我们有一个相当简单的 JSON API 模型,我们需要将我们的模型类注册到 JSONAPIFactory 中,您可以这样操作:

import JSONAPIModel

extension JSONAPIFactory {
    /// Default factory that registered with all models
    static var defaultFactory: JSONAPIFactory {
        let factory = JSONAPIFactory()
        factory.register(modelType: Location.self)
        factory.register(modelType: Employee.self)
        return factory
    }
}

然后,要从 JSON 加载数据,您可以编写以下代码:

import SwiftyJSON
import JSONAPIModel

let factory = JSONAPIFactory.defaultFactory
let json = try JSON(data: data)
let result = try factory.createModel(json["data"])
guard let location = result as? Location else {
    return
}
let store = JSONAPIStore(includedRecords: json["included"])
try location.loadIncluded(factory, store: store)

factory.createModeldata 键加载 JSON 数据。然后,我们将包含的对象加载到 JSONAPIStore 中。最后,调用 location.loadIncluded(factory, store: store) 使位置对象加载所有这些包含的对象。

安装

CocoaPods

要使用 CocoaPod 安装,将 Embassy 添加到您的 Podfile

pod 'JSONAPIModel', '~> 1.0'

Carthage

要使用 Carthage 安装,将 Embassy 添加到您的 Cartfile

github "envoy/JSONAPIModel" ~> 4.0

待办事项

Provider辅助器,用于加载数据和包含数组的数组

目前,加载数据基本上是一个手动过程。您需要从“data”密钥中获取JSON对象,并将其输入JSONAPIModel,然后获取“included”并创建一个存储,最后在您创建的对象上调用“loadIncluded”。在未来,也许提供整个过程的辅助器对用户来说更有意义。

更完善的解析错误报告

目前,解析错误基本上没有什么帮助。它可能简单地返回一个nil,或者抛出一个异常。处理得不好。我们应该提供更好的解析错误报告,这样我们就可以将这些错误写入日志文件以帮助排除故障。

对无效值的更好容错性

有时后端返回的nil值可能导致解析完全失败。如果后端工作不正常,这可能会使我们的客户陷入停机。虽然当给定的数据很糟糕时,让一切正常运行真的很困难,但至少在处理一些更常见的错误情况,如nil值或缺少键时,我们可以记录警告消息,而不是让应用程序崩溃。

更多自动测试

目前,我们只覆盖了一些相当简单的测试用例,在未来,我们应该覆盖更多。