JSONAPI сериализует вложенные отношения hasMany

Мы используем JSONAPI для этого проекта, но по [причинам] мы не можем обрабатывать его рекомендуемую структуру отношений в API, поэтому вместо этого мы обслуживаем и ожидаем их как вложенные объекты в следующем формате:

{
  "data":{
    "type":"video",
    "id":"55532284a1f9f909b0d11d73",

    "attributes":{
      "title":"Test",

      "transcriptions":{
        "type": "embedded",

        "data":[
          {
            "type":"transcription",
            "id":"203dee25-4431-42d1-a0ba-b26ea6938e75",

            "attributes":{
              "transcriptText":"Some transcription text here. And another sentence after it.",

              "cuepoints":{
                "type":"embedded",

                "data":[
                  {
                    "type":"cuepoint",
                    "id":"bb6b0434-bdc4-43e4-8010-66bdef5c432a",

                    "attributes":{
                      "text":"Some transcription text here."
                    }
                  },
                  {
                    "type":"cuepoint",
                    "id":"b663ee00-0ebc-4cf4-96fc-04d904bc1baf",

                    "attributes":{
                      "text":"And another sentence after it."
                    }
                  }
                ]
              }
            }
          }
        ]
      }
    }
  }
}

У меня есть следующая структура модели:

// models/video
export default DS.Model.extend({
  transcriptions: DS.hasMany('transcription')
)};

// models/transcription
export default DS.Model.extend({
  video: DS.belongsTo('video'),
  cuepoints: DS.hasMany('cuepoint')
});

// models/cuepoint
export default DS.Model.extend({
  transcription: DS.belongsTo('transcription')
);

Теперь нам нужно сохранить запись video и сериализовать содержащиеся в ней transcriptions и cuepoints. У меня есть следующий сериализатор, и он отлично работает для встраивания transcription в video, т.е. один уровень, но он мне нужен, чтобы затем встроить в него cuepoints.

export default DS.JSONAPISerializer.extend({
    serializeHasMany: function(record, json, relationship) {
      var hasManyRecords, key;
          key = relationship.key;
          hasManyRecords = Ember.get(record, key);

      if (hasManyRecords) {
        json.attributes[key] = {};

        hasManyRecords.forEach(function(item) {
          json.attributes[key].data = json.attributes[key].data || [];

          json.attributes[key].data.push({
            attributes: item._attributes,
            id: item.get('id'),
            type: item.get('type')
          });
        });
      } else {
        this._super(record, json, relationship);
      }
    }
  });

Проверяя свойства record, json и relationship в методе serializeHasMany, я ничего не вижу о вложенных отношениях, поэтому даже не уверен, что использую правильный метод.

Любые идеи, где я ошибаюсь с этим?


person Malabar Front    schedule 09.10.2015    source источник
comment
Вы изучали, как работает EmbeddedRecordsMixin? Может дать некоторое вдохновение о том, как действовать дальше.   -  person locks    schedule 09.10.2015


Ответы (2)


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

приложение/сериализаторы/cuepoint.js

import DS from 'ember-data';

export default DS.JSONAPISerializer.extend({

    payloadKeyFromModelName (modelName) {
        return modelName;
    },

    serialize (record, options) {
        return this._super(record, options).data;
    },

    serializeBelongsTo () {}

});

приложение/сериализаторы/transcription.js

import DS from 'ember-data';

export default DS.JSONAPISerializer.extend(DS.EmbeddedRecordsMixin, {

    attrs: {
        cuepoints: {
            serialize: 'records',
            deserialize: 'records'
        }
    },

    keyForAttribute (key, method) {
        return key;
    },

    payloadKeyFromModelName (modelName) {
        return modelName;
    },

    serialize (record, options) {
        let json = this._super(record, options);
        json.data.attributes.cuepoints = {
            type: 'embedded',
            data: json.data.cuepoints
        }
        delete json.data.cuepoints;
        return json.data;
    },

    serializeBelongsTo () {}

});

приложение/сериализаторы/video.js

import DS from 'ember-data';

export default DS.JSONAPISerializer.extend(DS.EmbeddedRecordsMixin, {

    attrs: {
        transcriptions: {
            serialize: 'records',
            deserialize: 'records'
        }
    },

    payloadKeyFromModelName (modelName) {
        return modelName;
    },

    serialize (record, options) {
        let json = this._super(record, options);
        json.data.attributes.transcriptions = {
            type: 'embedded',
            data: json.data.transcriptions
        }
        delete json.data.transcriptions;
        return json;
    },

    serializeBelongsTo () {}

});
person Artur Smirnov    schedule 12.10.2015
comment
Спасибо. Тогда нет ли способа сделать это в общем виде? Я действительно надеялся, что смогу рекурсивно перебирать отношения в методе serializeHasMany, но похоже, что это невозможно. - person Malabar Front; 12.10.2015
comment
@MalabarFront Это семантически неверно. Почему сериализатор видео должен знать, как сериализовать транскрипцию? Каждая модель должна быть сериализована собственным сериализатором. Так работает Эмбер. Вы не должны пытаться взломать его (даже путем доступа к закрытым свойствам и методам), так как это может вызвать огромные проблемы во время обновления. - person Artur Smirnov; 12.10.2015
comment
Поскольку наш API и структура данных очень строгие, форма всех данных — вложенных или нет — известна. По этой причине я чувствовал, что писать сериализатор для каждой модели — это безумие. Я надеялся написать сериализатор, который знал бы, как обрабатывать вложенные данные независимо от того, как называется модель. - person Malabar Front; 12.10.2015

Думаю, я понял это. Было несколько методов, о которых я не знал, для зацикливания отношений, и мне нужно было написать собственный метод serialize вместо того, чтобы просто переопределить метод serializeHasMany по умолчанию.

serialize(record) {
  // Set up the main data structure for the record to be serialized
  var JSON = {
    data: {
      id: record.id,
      type: record.modelName,
      attributes: {}
    }
  };

  // Find relationships in the record and serialize them into the JSON.data.attributes object
  JSON.data.attributes = this.serializeRelationships(JSON.data.attributes, record);

  // Loop through the record's attributes and insert them into the JSON.data.attributes object
  record.eachAttribute((attr) => {
    JSON.data.attributes[attr] = record.attr(attr);
  });

  // Return the fully serialized JSON data
  return JSON;
},

// Take a parent JSON object and an individual record, loops through any relationships in the record, and creates a JSONAPI resource object
serializeRelationships(JSON, record) {
  record.eachRelationship((key, relationship) => {
    if (relationship.kind === 'hasMany') {

      // Set up the relationship data structure
      JSON[relationship.key] = {
        data: []
      };

      // Gran any relationships in the record
      var embeddedRecords = record.hasMany(relationship.key);

      // Loop through the relationship's records and build a resource object
      if (embeddedRecords) {
        embeddedRecords.forEach((embeddedRecord) => {
          var obj = {
            id: embeddedRecord.id,
            type: embeddedRecord.modelName,
            attributes: {}
          }

          // Recursively check for relationships in the record
          obj.attributes = this.serializeRelationships(obj.attributes, embeddedRecord);

          // Loop through the standard attributes and populate the record.data.attributes object
          embeddedRecord.eachAttribute((attr) => {
            obj.attributes[attr] = embeddedRecord.attr(attr);
          });

          JSON[relationship.key].data.push(obj);
        });
      }
    }
  });

  return JSON;
}
person Malabar Front    schedule 16.10.2015