【问题标题】:Making Vapor API response JSON API Spec compliant使 Vapor API 响应 JSON API Spec 兼容
【发布时间】:2019-02-13 09:50:12
【问题描述】:

我有一个用 Vapor 编写的 API。我想遵循 JSON API 规范。

我正在努力理解如何以正确的格式创建我的响应对象。

例如,我希望我的回复具有这样的结构......

{
  "links": {
    "self": "http://example.com/dish",
    "next": "http://example.com/dish?page=2",
    "last": "http://example.com/dish?page=10"
  },
  "data": [{
    "title": "Spag Bol",
    "course": "main",
    "description": "BasGetti",
    "price": 3.9900000000000002
  },
  {
    "title": "Ice Cream",
    "course": "desert",
    "description": "Vanilla",
    "price": 0.98999999999999999
  }]
}

如果POST到这个端点我可以很简单的返回数据的内容(伪代码)

router.post(Dish.self, at: "api/dish") { req, data -> Future<Dish> in
    return Future.map(on: req, { () -> Dish in
        data.id = 001
        return data
    })
}

我尝试创建一个 ApiResponse 类并传入数据,以便我可以构建响应,但这不适用于错误 Cannot convert return expression of type 'ApiResonse' to return type 'Dish'

   router.post(Dish.self, at: "api/dish") { req, data -> Future<Dish> in
        return Future.map(on: req, { () -> Dish in
            data.id = 001
            return ApiResonse(links: Links(self: "http://google.com", next: "http://google.com", last: "http://google.com"), data: data)
        })
    }

我不确定我该怎么做。这些是尝试的类

final class Dish: Content {
    var id: Int?
    var title: String
    var description: String
    var course: String
    var price: Double

    init(title: String, description: String, course: String, price: Double) {
        self.title = title
        self.description = description
        self.course = course
        self.price = price
    }
}

struct Links {
    var `self`: String?
    var next: String?
    var last: String?
}

class ApiResonse {
    var links: Links?
    var data: Any

    init(links: Links, data: Any) {
        self.links = links
        self.data = data
    }
}

我需要使用泛型来设置响应类吗?有谁可以举个例子吗?

【问题讨论】:

    标签: swift generics vapor


    【解决方案1】:
    1. 复合对象ApiResponse中的每个classstruct都需要遵守Content协议。 Content 协议包括用于 JSON 解码和编码的 Codable 协议。

    2. 注意Any符合Codable协议,因此Any不能用作响应的任何组成部分。请参阅Vapor 3 Docs: "Using Content"Vapor 4 Docs: "Content" 了解更多详细信息。

      Vapor 3:所有内容类型(JSON、protobuf、URLEncodedForm、Multipart 等)都被同等对待。解析和序列化内容所需的只是 Codable 类或结构。

      Vapor 4:Vapor 的内容 API 允许您轻松地对 HTTP 消息中的 Codable 结构进行编码/解码。

    3. 完全符合Content 的对象或复合对象可用作ResponseEncodable 响应。

    4. 当每个路由端点解析为特定的Content 协议兼容类型时,ApiResponse 模型可以是通用

    下面代码的示例项目位于GitHub: VaporExamplesLab/Example-SO-VaporJsonResponse

    示例模型

    struct Dish: Content {
        var id: Int?
        var title: String
        var description: String
        var course: String
        var price: Double
        
        init(id: Int? = nil, title: String, description: String, course: String, price: Double) {
            self.id = id
            self.title = title
            self.description = description
            self.course = course
            self.price = price
        }
    }
    
    struct Links: Content {
        var current: String?
        var next: String?
        var last: String?
    }
    
    struct ApiResponse: Content {
        var links: Links?
        var dishes: [Dish]
        
        init(links: Links, dishes: [Dish]) {
            self.links = links
            self.dishes = dishes
        }
    }
    

    示例POST:返回ApiResponse

    router.post(Dish.self, at: "api/dish") { 
        (request: Request, dish: Dish) -> ApiResponse in
        var dishMutable = dish
        dishMutable.id = 001
        
        var links = Links()
        links.current = "http://example.com"
        links.next = "http://example.com"
        links.last = "http://example.com"
    
        return ApiResponse(links: links, dishes: [dishMutable])
    }
    

    示例POST:返回Future&lt;ApiResponse&gt;

    router.post(Dish.self, at: "api/dish-future") { 
        (request: Request, dish: Dish) -> Future<ApiResponse> in
        var dishMutable = dish
        dishMutable.id = 002
        
        var links = Links()
        links.current = "http://example.com"
        links.next = "http://example.com"
        links.last = "http://example.com"
        
        return Future.map(on: request, { 
            () -> ApiResponse in
            return ApiResponse(links: links, dishes: [dishMutable])
        }) 
    }
    

    收到 JSON 响应

    上述代码的响应正文产生以下内容:

    {
      "links": {
        "current": "http://example.com",
        "next": "http://example.com",
        "last": "http://example.com"
      },
      "dishes": [
        {
          "id": 1,
          "title": "Aztec Salad",
          "description": "Flavorful Southwestern ethos with sweet potatos and black beans.",
          "course": "salad",
          "price": 1.82
        }
      ]
    }
    

    通用模型

    struct ApiResponseGeneric<T> : Content where T: Content { 
        var links: Links?
        var data: T
        
        init(links: Links, data: T) {
            self.links = links
            self.data = data
        }
    }
    

    具体路线端点

    router.post(Dish.self, at: "api/dish-generic-future") { 
        (request: Request, dish: Dish) -> Future<ApiResponseGeneric<[Dish]>> in
        var dishMutable = dish
        dishMutable.id = 004
        
        var links = Links()
        links.current = "http://example.com"
        links.next = "http://example.com"
        links.last = "http://example.com"
        
        return Future.map(on: request, { 
            () -> ApiResponseGeneric<[Dish]> in
            return ApiResponseGeneric<[Dish]>(links: links, data: [dishMutable])
        }) 
    }
    

    【讨论】:

    • 哇。太感谢了。在你投入这个和提供的回购的时间里,我希望我能多次多次投票!
    • 是否可以使用多种类型的 ApiResponse?比如说我有一个 Dish 响应,并且在另一个端点上还有一个 Menu 响应?我可以分享 APIResponse 或每次创建新版本所需的内容吗?
    • @pgGriff 已将​​ApiResponse 与泛型一起使用的示例添加到答案中。
    • 我不知道你对我的帮助有多大。再次感谢你:)
    【解决方案2】:

    您需要将data 设为[Dish]

    class ApiResonse {
        var links: Links?
        var data: [Dish]
    
        init(links: Links, data: [Dish]) {
            self.links = links
            self.data = [Dish]
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-14
      • 1970-01-01
      • 2013-10-06
      • 2019-03-26
      • 2020-08-16
      • 2019-02-21
      相关资源
      最近更新 更多