From f14e3b7f647e5731f6e81f8ed579b14c430a7ad1 Mon Sep 17 00:00:00 2001 From: ckeboss Date: Mon, 7 Oct 2019 09:11:01 -0700 Subject: [PATCH 1/2] Add excludeData flag to serialize and serializeAsync --- README.md | 17 +++++++++ lib/JSONAPISerializer.js | 53 +++++++++++++++++++-------- test/unit/JSONAPISerializer.test.js | 56 +++++++++++++++++++---------- 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 5d0332d..9cd1761 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,23 @@ The output data will be : } ``` +There is an available argument `excludeData` that will exclude the `data` +property from the serialized object. This can be used in cases where you may +want to only include the `topLevelMeta` in your response, such as a `DELETE` +response with only a `meta` property, or other cases defined in the +JSON:API spec. + +```javascript +// Synchronously (blocking) +const result = Serializer.serialize('article', data, 'default', {count: 2}, true); + +// Asynchronously (non-blocking) +Serializer.serializeAsync('article', data, 'default', {count: 2}, true) + .then((result) => { + ... + }); +``` + Some others examples are available in [tests folders](https://github.com/danivek/json-api-serializer/blob/master/test/) ### Deserialize diff --git a/lib/JSONAPISerializer.js b/lib/JSONAPISerializer.js index 67f2a62..811ed60 100644 --- a/lib/JSONAPISerializer.js +++ b/lib/JSONAPISerializer.js @@ -73,9 +73,10 @@ module.exports = class JSONAPISerializer { * @param {object|object[]} data input data. * @param {string|object} [schema='default'] resource's schema name. * @param {object} [extraData] additional data that can be used in topLevelMeta options. + * @param {boolean} excludeData boolean that can be set to exclude the `data` property in serialized data. * @returns {object} serialized data. */ - serialize(type, data, schema, extraData) { + serialize(type, data, schema, extraData, excludeData) { // Support optional arguments (schema) if (arguments.length === 3) { if (typeof schema === 'object') { @@ -107,13 +108,21 @@ module.exports = class JSONAPISerializer { options = this.schemas[type][schema]; } + let dataProperty; + + if (excludeData) { + dataProperty = undefined; + } else if (isDynamicType) { + dataProperty = this.serializeMixedResource(options, data, included, extraData); + } else { + dataProperty = this.serializeResource(type, data, options, included, extraData); + } + return { jsonapi: options.jsonapiObject ? { version: '1.0' } : undefined, meta: this.processOptionsValues(data, extraData, options.topLevelMeta, 'extraData'), links: this.processOptionsValues(data, extraData, options.topLevelLinks, 'extraData'), - data: isDynamicType - ? this.serializeMixedResource(options, data, included, extraData) - : this.serializeResource(type, data, options, included, extraData), + data: dataProperty, included: included.size ? [...included.values()] : undefined }; } @@ -128,9 +137,10 @@ module.exports = class JSONAPISerializer { * @param {object|object[]} data input data. * @param {string} [schema='default'] resource's schema name. * @param {object} [extraData] additional data that can be used in topLevelMeta options. + * @param {boolean} excludeData boolean that can be set to exclude the `data` property in serialized data. * @returns {Promise} resolves with serialized data. */ - serializeAsync(type, data, schema, extraData) { + serializeAsync(type, data, schema, extraData, excludeData) { // Support optional arguments (schema) if (arguments.length === 3) { if (typeof schema === 'object') { @@ -173,6 +183,9 @@ module.exports = class JSONAPISerializer { */ function next() { setImmediate(() => { + if (excludeData) { + return resolve(); + } if (i >= arrayData.length) { return resolve(serializedData); } @@ -197,15 +210,27 @@ module.exports = class JSONAPISerializer { } next(); - }).then(result => ({ - jsonapi: options.jsonapiObject ? { version: '1.0' } : undefined, - meta: this.processOptionsValues(data, extraData, options.topLevelMeta, 'extraData'), - links: this.processOptionsValues(data, extraData, options.topLevelLinks, 'extraData'), - // If the source data was an array, we just pass the serialized data array. - // Otherwise we try to take the first (and only) item of it or pass null. - data: isDataArray ? result : result[0] || null, - included: included.size ? [...included.values()] : undefined - })); + }).then(result => { + let dataProperty; + + if (typeof result === 'undefined') { + dataProperty = undefined; + } else if (isDataArray) { + dataProperty = result; + } else { + dataProperty = result[0] || null; + } + + return { + jsonapi: options.jsonapiObject ? { version: '1.0' } : undefined, + meta: this.processOptionsValues(data, extraData, options.topLevelMeta, 'extraData'), + links: this.processOptionsValues(data, extraData, options.topLevelLinks, 'extraData'), + // If the source data was an array, we just pass the serialized data array. + // Otherwise we try to take the first (and only) item of it or pass null. + data: dataProperty, + included: included.size ? [...included.values()] : undefined + }; + }); } /** diff --git a/test/unit/JSONAPISerializer.test.js b/test/unit/JSONAPISerializer.test.js index adfb783..f396e7c 100644 --- a/test/unit/JSONAPISerializer.test.js +++ b/test/unit/JSONAPISerializer.test.js @@ -858,12 +858,31 @@ describe('JSONAPISerializer', function() { describe('serialize', function() { const Serializer = new JSONAPISerializer(); + const dataArray = [{ + id: 1, + title: 'Article 1', + }, { + id: 2, + title: 'Article 2', + }, { + id: 3, + title: 'Article 3', + }] + Serializer.register('articles', { - topLevelMeta: { - count: function(options) { - return options.count - } - } + topLevelMeta: (data, extraData) => ({ + count: extraData.count, + total: data.length + }) + }); + + it('should not include data property if excludeData is true', (done) => { + const serializedData = Serializer.serialize('articles', dataArray, 'default', {count: 2}, true); + expect(serializedData.data).to.be.undefined; + expect(serializedData.meta).to.have.property('count', 2) + expect(serializedData.meta).to.have.property('total', 3) + expect(serializedData.included).to.be.undefined; + done(); }); it('should serialize empty single data', function(done) { @@ -1003,11 +1022,10 @@ describe('JSONAPISerializer', function() { }] Serializer.register('articles', { - topLevelMeta: { - count: function(options) { - return options.count - } - } + topLevelMeta: (data, extraData) => ({ + count: extraData.count, + total: data.length, + }) }); it('should return a Promise', () => { @@ -1015,18 +1033,20 @@ describe('JSONAPISerializer', function() { expect(promise).to.be.instanceOf(Promise); }); - it('should serialize empty single data', () => - Serializer.serializeAsync('articles', {}) + it('should not include data property if excludeData is true', () => { + return Serializer.serializeAsync('articles', dataArray, 'default', {count: 2}, true) .then((serializedData) => { - expect(serializedData.data).to.eql(null); + expect(serializedData.data).to.be.undefined; + expect(serializedData.meta).to.have.property('count', 2) + expect(serializedData.meta).to.have.property('total', 3) expect(serializedData.included).to.be.undefined; - }) - ); + }); + }); - it('should serialize empty array data', () => - Serializer.serializeAsync('articles', []) + it('should serialize empty single data', () => + Serializer.serializeAsync('articles', {}) .then((serializedData) => { - expect(serializedData.data).to.eql([]); + expect(serializedData.data).to.eql(null); expect(serializedData.included).to.be.undefined; }) ); From e982da125c370cb4340f100e10f4875411487406 Mon Sep 17 00:00:00 2001 From: ckeboss Date: Tue, 8 Oct 2019 07:27:12 -0700 Subject: [PATCH 2/2] Fix JSDoc syntax --- lib/JSONAPISerializer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/JSONAPISerializer.js b/lib/JSONAPISerializer.js index 811ed60..a211535 100644 --- a/lib/JSONAPISerializer.js +++ b/lib/JSONAPISerializer.js @@ -73,7 +73,7 @@ module.exports = class JSONAPISerializer { * @param {object|object[]} data input data. * @param {string|object} [schema='default'] resource's schema name. * @param {object} [extraData] additional data that can be used in topLevelMeta options. - * @param {boolean} excludeData boolean that can be set to exclude the `data` property in serialized data. + * @param {boolean} [excludeData] boolean that can be set to exclude the `data` property in serialized data. * @returns {object} serialized data. */ serialize(type, data, schema, extraData, excludeData) { @@ -137,7 +137,7 @@ module.exports = class JSONAPISerializer { * @param {object|object[]} data input data. * @param {string} [schema='default'] resource's schema name. * @param {object} [extraData] additional data that can be used in topLevelMeta options. - * @param {boolean} excludeData boolean that can be set to exclude the `data` property in serialized data. + * @param {boolean} [excludeData] boolean that can be set to exclude the `data` property in serialized data. * @returns {Promise} resolves with serialized data. */ serializeAsync(type, data, schema, extraData, excludeData) {