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

ann*_*rns 11 rest ios siesta swift siesta-swift

我想使用ResponseTransformer(或其中一系列)自动将我的对象模型类映射到从Siesta服务返回的响应,以便我的Siesta资源是我的模型类的实例.我有一个类的工作实现,但我想知道在为每种类型的资源(模型)构建单独的ResponseTransformer之前是否有更安全,更智能或更有效的方法.

这是一个示例模型类:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class _GFSFAPI: Service {

    ...

    configure("/Challenge/*")    { $0.config.responseTransformers.add(ChallengeListTransformer()) }
}
Run Code Online (Sandbox Code Playgroud)

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

Pau*_*ell 8

你的方法很扎实!你基本上得到了它.您可以采取一些措施来简化变压器.

大图

听起来你已经掌握了这种权衡,但对于那些找到这个答案的人来说......你有两种一般的方法可供选择:

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

选项1更容易设置 - 只需在现场制作模型,您就完成了!

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

这适用于许多项目.但它有缺点:

  • 选项1为每个观察者乘以的每个事件实例化一个新的模型对象; 选项2仅根据"新数据"响应实例化模型对象.
  • 选项1中没有中心位置可以跟踪哪些路径映射到哪些模型对象.
  • 如果服务器未返回您期望的内容类型,则选项2会提供更好的错误.

如果(并且仅当)项目很小并且模型是轻量级的,我更喜欢选项1.

您可以在用户指南Pipeline部分找到有关选项2的大量文档.这是一个快速概述.

运用 configureTransformer

您可以ChallengeListTransformer使用configureTransformer(...)以下方式简化您:

configureTransformer("/Challenge/*") {
    (content: NSJSONConvertible, entity: Entity) throws -> [Challenge] in        
    let itemJSON = JSON(content)        
    return Challenge.parseChallengeList(itemJSON)
}
Run Code Online (Sandbox Code Playgroud)

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

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList(
        JSON($0.content as NSJSONConvertible))
}
Run Code Online (Sandbox Code Playgroud)

(注意configureTransformer设置transformErrors为false.这几乎可以肯定你想要的...除非你的服务器发送一个JSON"挑战"模型作为错误响应的主体!该transformErrors选项通常只适用于文本和JSON解析等通用变换器与内容类型相关联,而不是与路由相关联的内容类型.)

全球SwiftyJSON变压器

如果您正在使用SwiftyJSON(我也喜欢BTW),那么您可以将它集中应用于所有JSON响应:

private let SwiftyJSONTransformer =
    ResponseContentTransformer(skipWhenEntityMatchesOutputType: false)
        { JSON($0.content as AnyObject) }
Run Code Online (Sandbox Code Playgroud)

…然后:

service.configure {
    $0.config.responseTransformers.add(
        SwiftyJSONTransformer, contentTypes: ["*/json"])
}
Run Code Online (Sandbox Code Playgroud)

...进一步简化了每个路由的内容转换器:

configureTransformer("/Challenge/*") {
    Challenge.parseChallengeList($0.content)
}
Run Code Online (Sandbox Code Playgroud)

请注意,Swift的类型推断告诉Siesta,这个变换器需要一个JSONstruct作为输入,而Siesta使用它来将它标记为错误,如果它没有以这种方式从变换器管道中出来的话.与JSON相关的转换器都附加到*/json内容类型,因此如果服务器返回任何意外的内容,您的观察者会看到一个漂亮整洁的"嘿,那不是JSON!"错误.

有关所有这些内容的更深入信息,请参阅用户指南.

从资源中获取模型

正如Siesta API目前所示,您需要向下转换模型的内容:

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

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

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

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