将元数据添加到 RESTful JSON 响应的最佳实践是什么?

发布于 2024-12-18 11:46:27 字数 998 浏览 0 评论 0 原文

背景

我们正在构建一个 Restful API,它应以 JSON 形式返回数据对象。在大多数情况下,只需返回数据对象就可以了,但在某些情况下,f.ex.分页或验证,我们需要向响应添加一些元数据。

到目前为止我们所拥有的

我们已经包装了所有 json 响应,如下例所示:

{
    "metadata" :{
        "status": 200|500,
        "msg": "Some message here",
        "next": "http://api.domain.com/users/10/20"
        ...
    },
    "data" :{
        "id": 1001,
        "name": "Bob"
    }
}

优点

  • 我们可以向响应添加有用的元数据

缺点

  • 在大多数情况下,我们不需要元数据字段,而且它增加了 json 格式的复杂性
  • ,因为它不再是一个数据对象,而更像是一个封装的响应,我们不能立即在 f.exbackbone.js 中使用响应而不提取数据对象。

问题

将元数据添加到 json 响应的最佳实践是什么?

更新

到目前为止,我从下面的答案中得到了什么:

  • 删除metadata.status并返回http响应代码 改为 http 协议 (200, 500 ...)
  • 将错误消息添加到 http 500 响应的正文
  • 对于分页,我自然会拥有一些元数据来告诉分页结构,以及嵌套在该结构中的数据
  • 可以添加少量元数据到http标头(X-something)

Background

We are building a Restful API that should return data objects as JSON. In most of the cases it fine just to return the data object, but in some cases, f.ex. pagination or validation, we need to add some metadata to the response.

What we have so far

We have wrapped all json responses like this example:

{
    "metadata" :{
        "status": 200|500,
        "msg": "Some message here",
        "next": "http://api.domain.com/users/10/20"
        ...
    },
    "data" :{
        "id": 1001,
        "name": "Bob"
    }
}

Pros

  • We can add helpful metadata to the response

Cons

  • In most cases we don't need the metadata field, and it adds complexity to the json format
  • Since it's not a data object any more, but more like a enveloped response, we can not use the response right away in f.ex backbone.js without extracting the data object.

Question

What is the best practices to add metadata to a json response?

UPDATE

What I've got so far from answers below:

  • Remove the metadata.status an return the http response code in the
    http protocol instead (200, 500 ...)
  • Add error msg to body of an http 500 repsonse
  • For pagination i natural to have some metadata telling about the pagination structure, and the data nested in that structure
  • Small amount of meta data can be added to http header (X-something)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

时光沙漏 2024-12-25 11:46:27

您可以通过多种方式在 RESTful API 中传递元数据:

  1. Http 状态代码
  2. 标头
  3. 响应正文

对于metadata.status,请使用 Http 状态代码,这就是用途!
如果元数据是指整个响应,您可以将其添加为标头字段。
如果元数据仅引用响应的一部分,则必须将元数据作为对象的一部分嵌入。不要将整个响应包装在人造信封中,并将包装器拆分为数据和元数据。

最后,在整个 API 中保持一致与您所做的选择。

一个很好的例子是对整个集合进行 GET 分页。获取/项目
您可以返回集合大小和自定义标题中的当前页面。标准链接标头中的分页链接:

Link: <https://api.mydomain.com/v1/items?limit=25&offset=25>; rel=next

此方法的问题是当您需要在响应中添加引用特定元素的元数据时。在这种情况下,只需将其嵌入到对象本身中即可。为了采用一致的方法...始终将所有元数据添加到响应中。因此,回到 GET /items,假设每个项目都已创建并更新了元数据:

{
  items:[
    {
      "id":"w67e87898dnkwu4752igd",
      "message" : "some content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    },
    ......
    {
      "id":"asjdfiu3748hiuqdh",
      "message" : "some other content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    }
  ],
  "_total" :133,
  "_links" :[
     {
        "next" :{
           href : "https://api.mydomain.com/v1/items?limit=25&offset=25"
         } 
   ]
}

请注意,集合响应是一种特殊情况。如果将元数据添加到集合中,则该集合不能再作为数组返回,它必须是一个包含数组的对象。为什么是一个物体?因为您想添加一些元数据属性。

与各个项目中的元数据进行比较。没有什么接近包裹实体。您只需向资源添加一些属性即可。

一种约定是区分控制或元数据字段。您可以在这些字段前添加下划线。

You have several means to pass metadata in a RESTful API:

  1. Http Status Code
  2. Headers
  3. Response Body

For the metadata.status, use the Http Status Code, that's what's for!
If metadata is refers to the whole response you could add it as header fields.
If metadata refers only to part of the response, you will have to embed the metadata as part of the object.DON'T wrap the whole response in an artifical envelope and split the wrapper in data and metadata.

And finally, be consistent across your API with the choices you make.

A good example is a GET on a whole collection with pagination. GET /items
You could return the collection size, and current page in custom headers. And pagination links in standard Link Header:

Link: <https://api.mydomain.com/v1/items?limit=25&offset=25>; rel=next

The problem with this approach is when you need to add metadata referencing specific elements in the response. In that case just embed it in the object itself. And to have a consistent approach...add always all metadata to response. So coming back to the GET /items, imagine that each item has created and updated metadata:

{
  items:[
    {
      "id":"w67e87898dnkwu4752igd",
      "message" : "some content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    },
    ......
    {
      "id":"asjdfiu3748hiuqdh",
      "message" : "some other content",
      "_created": "2014-02-14T10:07:39.574Z",
      "_updated": "2014-02-14T10:07:39.574Z"
    }
  ],
  "_total" :133,
  "_links" :[
     {
        "next" :{
           href : "https://api.mydomain.com/v1/items?limit=25&offset=25"
         } 
   ]
}

Note that a collection response is an special case. If you add metadata to a collection, the collection can no longer be returned as an array, it must be an object with an array in it. Why an object? because you want to add some metadata attributes.

Compare with the metadata in the individual items. Nothing close to wrapping the entity. You just add some attributes to the resource.

One convention is to differentiate control or metadata fields. You could prefix those fields with an underscore.

祁梦 2024-12-25 11:46:27

按照@Charlie的评论:对于问题的分页部分,您仍然需要将元数据烘焙到响应中,但是这里的 statusmessage 属性有点多余的,因为它们已经被 HTTP 协议本身覆盖(状态 200 - 找到模型,404 - 未找到模型,403 - 不足privs,你明白了)(参​​见规范)。即使您的服务器返回错误条件,您仍然可以将 message 部分作为响应正文发送。这两个字段将满足您的大部分元数据需求。

就我个人而言,我倾向于 (ab) 使用自定义 HTTP 标头来处理较小的元数据(带有 X- 前缀),但我认为不切实际的限制非常低。

我已经扩展了一点这是一个范围较小的问题,但我认为这些观点对于这个问题仍然有效。

Along the lines of @Charlie's comment: for the pagination part of your question you still need to bake the metadata into the response somhow, but the status and message attributes here are somewhat redundant, since they are already covered by the HTTP protocol itself (status 200 - model found, 404 - model not found, 403 - insufficient privs, you get the idea) (see spec). Even if your server returns an error condition you can still send the message part as the response body. These two fields will cover quite much of your metadata needs.

Personally, I have tended towards (ab)using custom HTTP headers for smaller pieces of metadata (with an X- prefix), but I guess the limit where that gets unpractical is pretty low.

I've expanded a bit about this in a question with a smaller scope, but I think the points are still valid for this question.

小ぇ时光︴ 2024-12-25 11:46:27

我建议您阅读此页面 https://www.odata.org/ 您并非被迫使用OData 但他们的工作方式是 REST 良好实践的一个很好的例子。

I suggest you to read this page https://www.odata.org/ You are not forced to use OData but the way they do the work is a good example of good practice with REST.

甲如呢乙后呢 2024-12-25 11:46:27

我们有相同的用例,需要将分页元数据添加到 JSON 响应。我们最终在 Backbone 中创建了一个可以处理这些数据的集合类型,并在 Rails 端创建了一个轻量级包装器。此示例只是将元数据添加到集合对象中以供视图引用。

所以我们创建了一个像这样的 Backbone Collection 类

// Example response:
// { num_pages: 4, limit_value: 25, current_page: 1, total_count: 97
//   records: [{...}, {...}] }

PageableCollection = Backbone.Collection.extend({
  parse: function(resp, xhr)  {
    this.numPages = resp.num_pages;
    this.limitValue = resp.limit_value;
    this.currentPage = resp.current_page;
    this.totalCount = resp.total_count;
    return resp.records;
  }  
});

然后我们在 Rails 端创建了这个简单的类,以便在使用 Kaminari 分页时发出元数据

class PageableCollection
  def initialize (collection)
    @collection = collection
  end
  def as_json(opts = {})
    {
      :num_pages => @collection.num_pages 
      :limit_value => @collection.limit_value 
      :current_page => @collection.current_page,
      :total_count => @collection.total_count
      :records => @collection.to_a.as_json(opts)
    }
  end
end

您可以在像这样的控制器中使用它

class ThingsController < ApplicationController
  def index 
    @things = Thing.all.page params[:page]
    render :json => PageableCollection.new(@things)
  end
end

享受。希望您觉得它有用。

We had the same use case, in which we needed to add pagination metadata to a JSON response. We ended up creating a collection type in Backbone that could handle this data, and a lightweight wrapper on the Rails side. This example just adds the meta data to the collection object for reference by the view.

So we created a Backbone Collection class something like this

// Example response:
// { num_pages: 4, limit_value: 25, current_page: 1, total_count: 97
//   records: [{...}, {...}] }

PageableCollection = Backbone.Collection.extend({
  parse: function(resp, xhr)  {
    this.numPages = resp.num_pages;
    this.limitValue = resp.limit_value;
    this.currentPage = resp.current_page;
    this.totalCount = resp.total_count;
    return resp.records;
  }  
});

And then we created this simple class on the Rails side, to emit the meta data when paginated with Kaminari

class PageableCollection
  def initialize (collection)
    @collection = collection
  end
  def as_json(opts = {})
    {
      :num_pages => @collection.num_pages 
      :limit_value => @collection.limit_value 
      :current_page => @collection.current_page,
      :total_count => @collection.total_count
      :records => @collection.to_a.as_json(opts)
    }
  end
end

You use it in a controller like this

class ThingsController < ApplicationController
  def index 
    @things = Thing.all.page params[:page]
    render :json => PageableCollection.new(@things)
  end
end

Enjoy. Hope you find it useful.

飘落散花 2024-12-25 11:46:27

如何直接返回您想要的数据对象,例如 return:

{
    "id": 1001,
    "name": "Bob"
}

并在标头中返回元数据。

选项 1(所有元数据 JSON 一个标头):

X-METADATA = '{"status": 200|500,"msg": "Some message here","next": "http://api.domain.com/users/10/20"...}'

选项 2(每个元数据字段一个标头):

X-METADATA-STATUS = 200|500
X-METADATA-MSG = "Some message here",
X-METADATA-NEXT = "http://api.domain.com/users/10/20"
...

到目前为止,我和您一样使用一个包含两个字段的复杂 JSON,一个用于数据,一个用于元数据。但我想开始使用我建议的这种方式,我认为它会更容易。


请注意,某些服务器对 HTTP 标头有大小限制,例如: https://www.tutorialspoint.com/What-is-the-maximum-size-of-HTTP-header-values

How about returning directly the object that you want in data, like return:

{
    "id": 1001,
    "name": "Bob"
}

And return in headers the metadata.

Option 1 (one header for all metadata JSON):

X-METADATA = '{"status": 200|500,"msg": "Some message here","next": "http://api.domain.com/users/10/20"...}'

Option 2 (one header per each metadata field):

X-METADATA-STATUS = 200|500
X-METADATA-MSG = "Some message here",
X-METADATA-NEXT = "http://api.domain.com/users/10/20"
...

Until now I was using like you, a complex JSON with two fields, one for data and one for metadata. But I'm thinking in starting using this way that I suggested, I think it will be more easy.


Remind that some server have size limit for HTTP headers, like this example: https://www.tutorialspoint.com/What-is-the-maximum-size-of-HTTP-header-values

凹づ凸ル 2024-12-25 11:46:27

JSON:API 通过定义顶级 metadata 解决了这个问题 属性。

JSON:API solves this by defining top-level meta and data properties.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文