Каковы рекомендации по добавлению метаданных в ответ RESTful JSON? - PullRequest
38 голосов
/ 28 ноября 2011

Фон

Мы создаем 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.ex backbone.js без извлечения объекта данных.

Вопрос

Как лучше всего добавлять метаданные в ответ json?

UPDATE

Что у меня так далеко от ответов ниже:

  • Удалите metadata.status и верните код ответа http в протокол http вместо (200, 500 ...)
  • Добавить ошибку в текст сообщения http 500 repsonse
  • Для пагинации естественно иметь некоторые метаданные, рассказывающие о структуре пагинации, и данные, вложенные в эту структуру
  • Небольшое количество метаданных можно добавить в заголовок http (X-что-то)

Ответы [ 4 ]

9 голосов
/ 14 февраля 2014

У вас есть несколько способов передачи метаданных в RESTful API:

  1. Http Status Code
  2. Заголовки
  3. Тело ответа

Для metadata.status используйте Http Status Code, вот для чего! Если метаданные относятся ко всему ответу, вы можете добавить его в качестве полей заголовка. Если метаданные относятся только к части ответа, вам придется встраивать метаданные как часть объекта. НЕ оборачивать весь ответ в искусственный конверт и разбивать оболочку на данные и метаданные.

И, наконец, соответствует вашему API с выбором, который вы делаете.

Хороший пример - GET для всей коллекции с нумерацией страниц. 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"
         } 
   ]
}

Обратите внимание, что ответ коллекции - это особый случай. Если вы добавляете метаданные в коллекцию, коллекция больше не может быть возвращена в виде массива, это должен быть объект с массивом в нем. Почему объект? потому что вы хотите добавить некоторые атрибуты метаданных.

Сравните с метаданными в отдельных элементах. Ничего похожего на завертывание сущности. Вы просто добавляете некоторые атрибуты к ресурсу.

Одно из соглашений заключается в разграничении полей управления или метаданных. Вы можете добавить к этим полям знак подчеркивания.

4 голосов
/ 29 ноября 2011

В соответствии с комментариями @ Чарли: для разбиения на страницы вашего вопроса вам все еще нужно запекать метаданные в ответе, но атрибуты status и message здесь несколько избыточны, поскольку они уже рассмотрены.по самому протоколу HTTP (статус 200 - модель найдена, 404 - модель не найдена, 403 - недостаточно привилегий, вы понимаете) (см. spec ).Даже если ваш сервер возвращает условие ошибки, вы все равно можете отправить часть message в качестве тела ответа.Эти два поля покроют достаточно большую часть ваших потребностей в метаданных.

Лично я склонялся к (ab) использованию пользовательских заголовков HTTP для небольших фрагментов метаданных (с префиксом X-), но я предполагаю ограничениегде это становится непрактичным, довольно мало.

Я расширил немного об этом в вопросе с меньшей областью, но я думаю, что пункты все еще действительны для этого вопроса.

0 голосов
/ 11 апреля 2019

Я предлагаю вам прочитать эту страницу https://www.odata.org/ Вы не обязаны использовать OData, но то, как они работают, является хорошим примером хорошей практики с REST.

0 голосов
/ 29 ноября 2011

У нас был тот же вариант использования, в котором нам нужно было добавить метаданные пагинации в ответ 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

Наслаждайтесь.Надеюсь, вы найдете это полезным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...