我有一个控制器,其创建动作我想像这样接受JSON:
{ "foo": "bar" }
或类似这样:
{ "widget": { "foo": "bar" } }
也就是说,我要接受小部件或包含在包含对象中的小部件。当前,控制器的create动作看起来很像这样:
func createHandler(_ req: Request) throws -> Future<Widget> {
do {
return try req.content.decode(WidgetCreateHolder.self).flatMap(to: Widget.self) {
return createWidget(from: $0.widget)
}
} catch DecodingError.keyNotFound {
return try req.content.decode(WidgetCreateObject.self).flatMap(to: Widget.self) {
return createWidget(from: $0)
}
}
}
WidgetCreateObject
看起来像:
struct WidgetCreateObject { var foo: String? }
[WidgetCreateHolder
看起来像:
struct WidgetCreateHolder { var widget: WidgetCreateObject }
也就是说,我的create动作应该try
创建一个持有人,但是如果失败,它将捕获错误并尝试仅创建内部对象(WidgetCreateObject
)。但是,当我将此代码部署到Heroku并仅使用内部对象JSON发出请求时,就会在日志中得到此信息:
[ ERROR ] DecodingError.keyNotFound: Value required for key 'widget'. (ErrorMiddleware.swift:26)
即使我试图捕获该错误!
如何获得创建动作以接受两种不同格式的JSON对象?
想通!
decode
方法返回一个Future
,这样实际的解码(以及因此的错误)就会在以后发生,而不是在do / catch期间发生。这意味着无法通过此do catch捕获错误。
[幸运的是,Future
有一系列以catch
开头的方法;我感兴趣的是catchFlatMap
,它接受Error -> Future<Decodable>
的闭包。此方法“捕获”被调用的Future中引发的错误,并使用任何下游Future中的结果将错误传递给闭包。
所以我能够将代码更改为:
func createHandler(_ req: Request) throws -> Future<Widget> {
return try req.content.decode(WidgetCreateHolder.self).catchFlatMap({ _ in
return try req.content.decode(WidgetCreateObject.self).map(to: WidgetCreateHolder.self) {
return WidgetCreateHolder(widget: $0)
}
}).flatMap(to: Widget.self) {
return createWidget(from: $0.widget)
}
}