Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 39 additions & 14 deletions lib/JSONAPISerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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
};
}
Expand All @@ -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') {
Expand Down Expand Up @@ -173,6 +183,9 @@ module.exports = class JSONAPISerializer {
*/
function next() {
setImmediate(() => {
if (excludeData) {
return resolve();
}
if (i >= arrayData.length) {
return resolve(serializedData);
}
Expand All @@ -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
};
});
}

/**
Expand Down
56 changes: 38 additions & 18 deletions test/unit/JSONAPISerializer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -1003,30 +1022,31 @@ 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', () => {
const promise = Serializer.serializeAsync('articles', {});
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;
})
);
Expand Down