diff --git a/README.md b/README.md index 806a85c..b00c8fc 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Serializer.register(type, options); - **id** (optional): The key to use as the reference. Default = 'id'. - **blacklist** (optional): An array of blacklisted attributes. Default = []. - **whitelist** (optional): An array of whitelisted attributes. Default = []. +- **jsonapiObject** (optional): Enable/Disable [JSON API Object](http://jsonapi.org/format/#document-jsonapi-object). Default = true. - **links** (optional): Describes the links inside data. It can be: - An *object* (values can be string or function). - A *function* with one argument `function(data) { ... }` or with two arguments `function(data, extraData) { ... }` @@ -385,6 +386,7 @@ relationships: { If your data contains one or multiple objects of different types, it's possible to define a configuration object instead of the type-string as the first argument of ```serialize``` and ```serializeAsync``` with these options: - **type** (required): A *string* for the path to the key to use to determine type or a *function* deriving a type-string from each data-item. +- **jsonapiObject** (optional): Enable/Disable [JSON API Object](http://jsonapi.org/format/#document-jsonapi-object). Default = true. - **topLevelMeta** (optional): Describes the top-level meta. It can be: - An *object* (values can be string or function). - A *function* with one argument `function(extraData) { ... }` or with two arguments `function(data, extraData) { ... }` diff --git a/lib/JSONAPISerializer.js b/lib/JSONAPISerializer.js index b7ce11a..ef3be6f 100644 --- a/lib/JSONAPISerializer.js +++ b/lib/JSONAPISerializer.js @@ -53,6 +53,7 @@ module.exports = class JSONAPISerializer { unconvertCase: joi.string().valid('kebab-case', 'snake_case', 'camelCase'), blacklistOnDeserialize: joi.array().items(joi.string()).single().default([]), whitelistOnDeserialize: joi.array().items(joi.string()).single().default([]), + jsonapiObject: joi.boolean().default(true), }).required(); const validated = joi.validate(options, optionsSchema); @@ -77,6 +78,7 @@ module.exports = class JSONAPISerializer { type: joi.alternatives([joi.func(), joi.string()]).required(), topLevelLinks: joi.alternatives([joi.func(), joi.object()]).default({}), topLevelMeta: joi.alternatives([joi.func(), joi.object()]).default({}), + jsonapiObject: joi.boolean().default(true), }).required(); const validated = joi.validate(options, schema); @@ -134,13 +136,12 @@ module.exports = class JSONAPISerializer { const included = []; let serializedData; - let topLevelOption; + let options; if (_.isPlainObject(type)) { // Serialize data with the dynamic type - const typeOption = this.validateDynamicTypeOptions(type); + options = this.validateDynamicTypeOptions(type); // Override top level data - topLevelOption = { topLevelMeta: typeOption.topLevelMeta, topLevelLinks: typeOption.topLevelLinks }; - serializedData = this.serializeMixedData(typeOption, data, included, extraData); + serializedData = this.serializeMixedData(options, data, included, extraData); } else { // Serialize data with the defined type if (!this.schemas[type]) { throw new Error(`No type registered for ${type}`); @@ -150,18 +151,15 @@ module.exports = class JSONAPISerializer { throw new Error(`No schema ${schema} registered for ${type}`); } - const resourceOpts = this.schemas[type][schema]; - topLevelOption = { topLevelMeta: resourceOpts.topLevelMeta, topLevelLinks: resourceOpts.topLevelLinks }; - serializedData = this.serializeData(type, data, resourceOpts, included, extraData); + options = this.schemas[type][schema]; + serializedData = this.serializeData(type, data, options, included, extraData); } return { - jsonapi: { - version: '1.0', - }, - meta: this.processOptionsValues(data, extraData, topLevelOption.topLevelMeta, 'extraData'), - links: this.processOptionsValues(data, extraData, topLevelOption.topLevelLinks, 'extraData'), + jsonapi: options.jsonapiObject ? { version: '1.0' } : undefined, + meta: this.processOptionsValues(data, extraData, options.topLevelMeta, 'extraData'), + links: this.processOptionsValues(data, extraData, options.topLevelLinks, 'extraData'), data: serializedData, included: this.serializeIncluded(included), }; @@ -194,15 +192,12 @@ module.exports = class JSONAPISerializer { const included = []; const isDataArray = Array.isArray(data); const isDynamicType = _.isPlainObject(type); - let topLevelOption; let serializedData; let serializedIncludes; - let resourceOpts; + let options; if (isDynamicType) { - type = this.validateDynamicTypeOptions(type); - // Override top level data - topLevelOption = { topLevelMeta: type.topLevelMeta, topLevelLinks: type.topLevelLinks }; + options = this.validateDynamicTypeOptions(type); } else { if (!this.schemas[type]) { throw new Error(`No type registered for ${type}`); @@ -212,8 +207,7 @@ module.exports = class JSONAPISerializer { throw new Error(`No schema ${schema} registered for ${type}`); } - resourceOpts = this.schemas[type][schema]; - topLevelOption = { topLevelMeta: resourceOpts.topLevelMeta, topLevelLinks: resourceOpts.topLevelLinks }; + options = this.schemas[type][schema]; } // Convert data into stream with serialization-transform. Single objects @@ -225,7 +219,7 @@ module.exports = class JSONAPISerializer { // Serialize a single item of the data-array. const serializedItem = isDynamicType ? this.serializeMixedData(type, item, included, extraData) - : this.serializeData(type, item, resourceOpts, included, extraData); + : this.serializeData(type, item, options, included, extraData); // If the serialized item is null, we won't push it to the stream, // as pushing a null-value causes streams to end. @@ -250,11 +244,9 @@ module.exports = class JSONAPISerializer { .then((result) => { serializedIncludes = result; return { - jsonapi: { - version: '1.0', - }, - meta: this.processOptionsValues(data, extraData, topLevelOption.topLevelMeta, 'extraData'), - links: this.processOptionsValues(data, extraData, topLevelOption.topLevelLinks, 'extraData'), + 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 ? serializedData : (serializedData[0] || null), diff --git a/test/integration/examples.js b/test/integration/examples.js index e3586b9..5dc2604 100644 --- a/test/integration/examples.js +++ b/test/integration/examples.js @@ -73,7 +73,7 @@ describe('Examples', function() { var serializedData = Serializer.serialize('article', articlesData, { count: 2 }); - expect(serializedData).to.have.property('jsonapi').to.have.property('version'); + expect(serializedData).to.have.property('jsonapi').to.have.property('version').to.eql('1.0'); expect(serializedData).to.have.property('meta').to.have.property('count').to.eql(2); expect(serializedData).to.have.property('links').to.have.property('self').to.eql('/articles'); expect(serializedData).to.have.property('data'); diff --git a/test/unit/JSONAPISerializer.test.js b/test/unit/JSONAPISerializer.test.js index ac94ec7..ea2a931 100644 --- a/test/unit/JSONAPISerializer.test.js +++ b/test/unit/JSONAPISerializer.test.js @@ -937,6 +937,39 @@ describe('JSONAPISerializer', function() { expect(serializedData.included).to.be.undefined; done(); }); + + it('should serialize with option \'jsonapiObject\' disabled', function(done) { + const Serializer = new JSONAPISerializer(); + Serializer.register('article', { + jsonapiObject: false + }); + + const data = { + id: '1', + title: 'JSON API paints my bikeshed!', + body: 'The shortest article. Ever.' + }; + + const serializedData = Serializer.serialize('article', data); + expect(serializedData).have.property('jsonapi').to.be.undefined; + done(); + }); + + it('should serialize mixed data with option \'jsonapiObject\' disabled', function(done) { + const Serializer = new JSONAPISerializer(); + Serializer.register('article'); + + const data = { + id: '1', + type: 'article', + title: 'JSON API paints my bikeshed!', + body: 'The shortest article. Ever.' + }; + + const serializedData = Serializer.serialize({ type: 'type', jsonapiObject: false }, data); + expect(serializedData).have.property('jsonapi').to.be.undefined; + done(); + }); }); describe('serializeAsync', function() {