Skip to content

Commit

Permalink
Add convertCase option
Browse files Browse the repository at this point in the history
closes #3
  • Loading branch information
danivek committed Nov 16, 2016
1 parent 643ba75 commit de1c018
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 50 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Serializer.register(type, options);
- **type**: The type to use for serializing the embedded resource (type need to be register)
- **schema** (optional): A custom schema for serializing the embedded resource. If no schema define, it use the default one.
- **links** (optional): An *object* or a *function* that describes the links for the relationship. (If it is an object values can be string or function).
- **convertCase** (optional): Case conversion for serializing data. Value can be : `kebab-case`, `snake_case`, `camelCase`

## Usage

Expand Down
109 changes: 78 additions & 31 deletions lib/HALSerializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = class HALSerializer {
})).default({}),
topLevelLinks: joi.alternatives([joi.func(), joi.object()]).default({}),
topLevelMeta: joi.alternatives([joi.func(), joi.object()]).default({}),
convertCase: joi.string(),
}).required();

const validated = joi.validate(options, optionsSchema);
Expand Down Expand Up @@ -104,7 +105,13 @@ module.exports = class HALSerializer {
}

// Remove embedded and blacklist attributes
return _.pick(data, _.difference(Object.keys(data), _.concat(Object.keys(options.embedded), options.blacklist)));
let serializedAttributes = _.pick(data, _.difference(Object.keys(data), _.concat(Object.keys(options.embedded), options.blacklist)));

if (options.convertCase) {
serializedAttributes = this._convertCase(serializedAttributes, options.convertCase);
}

return serializedAttributes;
}

serializeEmbedded(data, options, links) {
Expand All @@ -113,6 +120,9 @@ module.exports = class HALSerializer {
_.forOwn(options.embedded, (rOptions, embedded) => {
const schema = rOptions.schema || 'default';
const serializeEmbeddedResource = this.serializeEmbeddedResource(embedded, data[embedded], rOptions, this.schemas[options.embedded[embedded].type][schema], links);

embedded = (options.convertCase) ? this._convertCase(embedded, options.convertCase) : embedded;

if (!_.isEmpty(serializeEmbeddedResource)) {
_.set(SerializedEmbedded, embedded, serializeEmbeddedResource);
}
Expand Down Expand Up @@ -164,34 +174,71 @@ module.exports = class HALSerializer {
}

/**
* Process options values.
* Allows options to be an object or a function.
*
* @method JSONAPISerializer#processOptionsValues
* @private
* @param {Object} data data passed to functions options
* @param {Object} options configuration options.
* @return {Object}
*/
processOptionsValues(data, options) {
let processedOptions;
if (_.isFunction(options)) {
processedOptions = options(data);
} else {
processedOptions = _.mapValues(options, value => {
let processed;
if (_.isFunction(value)) {
processed = value(data);
} else {
processed = value;
}
return processed;
});
}

// Clean all undefined values
processedOptions = _.omitBy(processedOptions, _.isUndefined);

return !_.isEmpty(processedOptions) ? processedOptions : undefined;
}
* Process options values.
* Allows options to be an object or a function.
*
* @method HALSerializer#processOptionsValues
* @private
* @param {Object} data data passed to functions options
* @param {Object} options configuration options.
* @return {Object}
*/
processOptionsValues(data, options) {
let processedOptions;
if (_.isFunction(options)) {
processedOptions = options(data);
} else {
processedOptions = _.mapValues(options, value => {
let processed;
if (_.isFunction(value)) {
processed = value(data);
} else {
processed = value;
}
return processed;
});
}

// Clean all undefined values
processedOptions = _.omitBy(processedOptions, _.isUndefined);

return !_.isEmpty(processedOptions) ? processedOptions : undefined;
}

/**
* Recursively convert object keys case
*
* @method HALSerializer#_convertCase
* @private
* @param {Object|Object[]|string} data to convert
* @param {string} convertCaseOptions can be snake_case', 'kebab-case' or 'camelCase' format.
* @return {Object}
*/
_convertCase(data, convertCaseOptions) {
let converted;
if (_.isArray(data) || _.isPlainObject(data)) {
converted = _.transform(data, (result, value, key) => {
if (_.isArray(value) || _.isPlainObject(value)) {
result[this._convertCase(key, convertCaseOptions)] = this._convertCase(value, convertCaseOptions);
} else {
result[this._convertCase(key, convertCaseOptions)] = value;
}
});
} else {
switch (convertCaseOptions) {
case 'snake_case':
converted = _.snakeCase(data);
break;
case 'kebab-case':
converted = _.kebabCase(data);
break;
case 'camelCase':
converted = _.camelCase(data);
break;
default: // Do nothing
}
}

return converted;
}
};
120 changes: 101 additions & 19 deletions test/unit/HALSerializer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,29 @@ describe('HALSerializer', function() {
expect(serializedEmbedded.comments[0]).to.have.property('_links').to.be.undefined;
done();
});

it('should return relationships with the convertCase options', function(done) {
const Serializer = new HALSerializer();
const links = {};
Serializer.register('author');
Serializer.register('articles', {
convertCase: 'kebab-case',
embedded: {
articleAuthor: {
type: 'author',
}
}
});
const included = [];
const serializedEmbedded = Serializer.serializeEmbedded({
id: '1',
articleAuthor: {
id: '1'
},
}, Serializer.schemas.articles.default, links);
expect(serializedEmbedded).to.have.property('article-author');
done();
});
});

describe('serializeAttributes', function() {
Expand All @@ -262,6 +285,78 @@ describe('HALSerializer', function() {
expect(serializedAttributes).to.have.property('body');
done();
});

it('should convert attributes to kebab-case format', function(done) {
const Serializer = new HALSerializer();
Serializer.register('articles', {
convertCase: 'kebab-case'
});
const data = {
id: '1',
firstName: 'firstName',
lastName: 'lastName',
articles: [{
createdAt: '2016-06-04T06:09:24.864Z'
}],
address: {
zipCode: 123456
}
};
const serializedAttributes = Serializer.serializeAttributes(data, Serializer.schemas.articles.default);
expect(serializedAttributes).to.have.property('first-name');
expect(serializedAttributes).to.have.property('last-name');
expect(serializedAttributes.articles[0]).to.have.property('created-at');
expect(serializedAttributes.address).to.have.property('zip-code');
done();
});

it('should convert attributes to snake_case format', function(done) {
const Serializer = new HALSerializer();
Serializer.register('articles', {
convertCase: 'snake_case'
});
const data = {
id: '1',
firstName: 'firstName',
lastName: 'lastName',
articles: [{
createdAt: '2016-06-04T06:09:24.864Z'
}],
address: {
zipCode: 123456
}
};
const serializedAttributes = Serializer.serializeAttributes(data, Serializer.schemas.articles.default);
expect(serializedAttributes).to.have.property('first_name');
expect(serializedAttributes).to.have.property('last_name');
expect(serializedAttributes.articles[0]).to.have.property('created_at');
expect(serializedAttributes.address).to.have.property('zip_code');
done();
});

it('should convert attributes to camelCase format', function(done) {
const Serializer = new HALSerializer();
Serializer.register('articles', {
convertCase: 'camelCase'
});
const data = {
id: '1',
'first-name': 'firstName',
'last-name': 'lastName',
articles: [{
'created-at': '2016-06-04T06:09:24.864Z'
}],
address: {
'zip-code': 123456
}
};
const serializedAttributes = Serializer.serializeAttributes(data, Serializer.schemas.articles.default);
expect(serializedAttributes).to.have.property('firstName');
expect(serializedAttributes).to.have.property('lastName');
expect(serializedAttributes.articles[0]).to.have.property('createdAt');
expect(serializedAttributes.address).to.have.property('zipCode');
done();
});
});

describe('processOptionsValues', function() {
Expand Down Expand Up @@ -313,45 +408,32 @@ describe('HALSerializer', function() {
}
});

/*it('should serialize empty single data', function(done) {
const serializedData = Serializer.serialize('articles', {});
expect(serializedData).to.eql(null);
done();
});
it('should serialize empty array data', function(done) {
const serializedData = Serializer.serialize('articles', []);
expect(serializedData).to.eql([]);
done();
});
it('should serialize with extra options as the third argument', function(done) {
const serializedData = Serializer.serialize('articles', [], {
count: 0
});
expect(serializedData).to.eql([]);
expect(serializedData).to.have.property('count').to.eql(0);
done();
});

it('should serialize with a custom schema', function(done) {
const Serializer = new HALSerializer();
Serializer.register('articles', 'only-title', {
whitelist: ['title']
whitelist: ['id', 'title']
});

const data = {
id: "1",
title: "JSON API paints my bikeshed!",
title: "Hal !",
body: "The shortest article. Ever."
};

const serializedData = Serializer.serialize('articles', data, 'only-title');
expect(serializedData.data).to.have.property('id', '1');
expect(serializedData.data).to.have.property('title');
expect(serializedData.data).to.not.have.property('body');
expect(serializedData).to.have.property('id', '1');
expect(serializedData).to.have.property('title');
expect(serializedData).to.not.have.property('body');
done();
});*/
});

it('should throw an error if type as not been registered', function(done) {
expect(function() {
Expand Down

0 comments on commit de1c018

Please sign in to comment.