将 REST URL 模式映射到 Siesta 框架的模型对象的最佳方法是什么?

What is the best way to map REST URL Patterns to Model Objects for the Siesta framework?

我想使用 ResponseTransformer(或其中的一系列)自动将我的对象模型 classes 映射到从 Siesta 服务返回的响应,这样我的 Siesta 资源就是我的实例型号 classes。我有一个 class 的有效实现,但我想知道在为每种类型的资源(模型)构建单独的 ResponseTransformer 之前是否有更安全、更智能或更有效的方法来执行此操作。

这是一个示例模型 class:

import SwiftyJSON

class Challenge {
    var id:String?
    var name:String?

    init(fromDictionary:JSON) {
        if let challengeId = fromDictionary["id"].int {
            self.id = String(challengeId)
        }
        self.name = fromDictionary["name"].string
    }
}

extension Challenge {

    class func parseChallengeList(fromJSON:JSON) -> [Challenge] {
        var list = [Challenge]()

        switch fromJSON.type {
        case .Array:
            for itemDictionary in fromJSON.array! {
                let item = Challenge(fromDictionary: itemDictionary)
                list.append(item)
            }
        case .Dictionary:
            list.append(Challenge(fromDictionary: fromJSON))
        default: break
        }

        return list
    }
}

这是我构建的 ResponseTransformer,用于映射来自 returns 此模型类型的集合或此模型类型的单个实例的任何端点的响应:

public func ChallengeListTransformer(transformErrors: Bool = true) -> ResponseTransformer {
    return ResponseContentTransformer(transformErrors: transformErrors)
        {
            (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
            let itemJSON = JSON(content)        
            return Challenge.parseChallengeList(itemJSON)
    }
}

最后,这是我在配置 Siesta 服务时所做的 URL 模式映射:

class _GFSFAPI: Service {

    ...

    configure("/Challenge/*")    { [=13=].config.responseTransformers.add(ChallengeListTransformer()) }
}

我计划为每种模型类型构建一个单独的 ResponseTransformer,然后将每个 URL 模式分别映射到该转换器。这是最好的方法吗?顺便说一句,我对新的 Siesta 框架非常兴奋。我喜欢面向资源的 REST 网络库的想法。

你的方法很可靠!你基本上明白了。您可以做一些事情来简化变形金刚。

大图

听起来您已经掌握了这种权衡,但对于找到此答案的其他人……您有两种通用方法可供选择:

  1. 在您的 Siesta 观察器中构建您的模型对象,或者
  2. 在转换器中构建模型对象。

选项 1 更易于设置 — 只需在现场制作模型,就大功告成了!

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = Challenge.parseChallengeList(
        JSON(resource.latestData?.jsonDict))
    ...
}

这适用于许多项目。但是,它有缺点:

  • 选项1为每个事件乘以每个观察者实例化一个新的模型对象;选项 2 仅根据“新数据”响应实例化模型对象。
  • 选项 1 中没有中心位置来跟踪哪些路由映射到哪些模型对象。
  • 如果服务器没有 return 您期望的内容类型,选项 2 会给出更好的错误。

如果(且仅当)项目较小且模型较轻时,我更喜欢选项 1。

您将在 Pipeline section of the user guide 中找到有关选项 2 的大量文档。这是一个快速概述。

使用configureTransformer

您可以使用 configureTransformer(...):

来简化您的 ChallengeListTransformer
configureTransformer("/Challenge/*") {
    (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
    let itemJSON = JSON(content)        
    return Challenge.parseChallengeList(itemJSON)
}

但是等等,还有更多!观看 Swift 惊人的类型推理切片和切块:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList(
        JSON([=12=].content as NSJSONConvertible))
}

(请注意 configureTransformertransformErrors 设置为 false。这几乎肯定是你想要的......除非你的服务器发送一个 JSON “挑战”模型作为错误的主体response!transformErrors 选项通常仅适用于与内容类型关联的通用转换器,如文本和 JSON 解析,而不是附加到路由的转换器。)

全局SwiftyJSON变压器

如果您正在使用 SwiftyJSON(我也喜欢,顺便说一句),那么您可以将它整体应用于所有 JSON 回复:

private let SwiftyJSONTransformer =
    ResponseContentTransformer(skipWhenEntityMatchesOutputType: false)
        { JSON([=13=].content as AnyObject) }

…然后:

service.configure {
    [=14=].config.responseTransformers.add(
        SwiftyJSONTransformer, contentTypes: ["*/json"])
}

…这进一步简化了每条路线的内容转换器:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList([=15=].content)
}

请注意,Swift 的类型推断告诉 Siesta,此转换器需要一个 JSON 结构作为输入,如果它没有从变压器管道就是这样。 JSON 相关的转换器都附加到 */json 内容类型,所以如果服务器 return 有任何意外,你的观察者会看到一个漂亮的整洁“嘿,那不是 JSON !错误。

See the user guide 了解关于这一切的更深入信息。

从资源中获取模型

由于Siesta API目前的情况,您需要向下转换模型的内容:

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = resource.latestData?.content as? [Challenge]
    ...
}

或者,您可以使用 TypedContentAccessors 协议扩展方法同时进行转换并在数据尚未存在时获取默认值 转换失败。例如,如果没有挑战,此代码默认为空数组:

func resourceChanged(resource: Resource, event: ResourceEvent) {
    let challenges = resource.typedContent(ifNone: [Challenge]())
    ...
}

Siesta 目前不提供将模型类型绑定到资源的静态类型方式;你必须做演员。这是因为 Swift 的类型系统的限制阻止了一些通用资源类型(例如 Resource<[Challenge]>)在实践中可行。希望 Swift 3 解决了这些问题,以便某些未来版本的 Siesta 可以提供这些问题。 更新: Swift 3 已完成必要的泛型改进,因此希望在 Swift 4 中……