Skip to content

Instantly share code, notes, and snippets.

@gnapse
Last active November 29, 2018 14:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gnapse/27655f4dfc1533903f70383f8e145a38 to your computer and use it in GitHub Desktop.
Save gnapse/27655f4dfc1533903f70383f8e145a38 to your computer and use it in GitHub Desktop.
Function to convert objects back to jsonapi format
/**
* Converts a given object to a jsonapi-compliant version of it, suitable to be used in api calls
*
* Specifying relationship attributes
*
* Each property in the `relationshipAttrs` argument correspondes to an attribute name in `object`.
* The value should be either a string or a small object specifying `name` (optional) and `type`.
*
* If the value is a string, this is used both for the name of the relationship in the resulting
* payload object, and for the type of the objects related. However, if the value of a
* `relationshipAttrs` property is an object with properties `type` and possibly also `name`, then
* these are used for the types of related objects, and for the name of the relationship,
* respectively. If the `name` is omitted, the name of the attribute is used.
*
* All attributes from the original object that were not specified to be interpreted as
* relationships are kept intact and returned as attributes. The object returned by this function
* are of the form `{ attributes: { ... }, relationships: { ... } }`, with each of these two parts
* being present only if not empty.
*
* See the tests of this function for examples of how all this works.
*
* @param {Object} object the object to convert to a jsonapi-compliant payload
* @param {Object} relationshipAttrs an object describing which attributes should be expressed as
* relationships, and how
* @returns {Object} the original object expressed as a jsonapi-compliant object
*/
export default function buildPayload(object, relationshipAttrs = {}) {
let attributes = undefined;
let relationships = undefined;
Object.keys(object).forEach(attr => {
if (attr === 'id') {
return;
}
if (relationshipAttrs[attr]) {
const { type, name } =
typeof relationshipAttrs[attr] === 'string'
? { type: relationshipAttrs[attr], name: relationshipAttrs[attr] }
: relationshipAttrs[attr];
relationships = relationships || {};
const processItem = item =>
typeof item === 'object' ? { id: item.id, type } : { id: item, type };
if (Array.isArray(object[attr])) {
const data = object[attr].map(processItem);
relationships[name || attr] = { data };
} else {
relationships[name || attr] = { data: processItem(object[attr]) };
}
} else {
attributes = attributes || {};
attributes[attr] = object[attr];
}
});
return { attributes, relationships };
}
import buildPayload from './buildPayload';
describe('buildPayload', () => {
const item = {
id: '1',
name: 'Jane Doe',
appRoles: ['2', '3'],
ownerId: '4',
};
it('works with empty object', () => {
expect(buildPayload({})).toEqual({});
});
it('separates specified relationships as expected', () => {
expect(
buildPayload(item, { appRoles: 'app_roles', ownerId: 'owner' })
).toEqual({
attributes: { name: 'Jane Doe' },
relationships: {
app_roles: {
data: [
{ type: 'app_roles', id: '2' },
{ type: 'app_roles', id: '3' },
],
},
owner: {
data: {
type: 'owner',
id: '4',
},
},
},
});
});
it('omits the attributes part if none present', () => {
expect(buildPayload({ ownerId: '4' }, { ownerId: 'owner' })).toEqual({
relationships: { owner: { data: { type: 'owner', id: '4' } } },
});
});
it('omits the relationships part if none present', () => {
expect(buildPayload(item, { parts: 'parts' })).toEqual({
attributes: { name: 'Jane Doe', ownerId: '4', appRoles: ['2', '3'] },
});
expect(buildPayload(item)).toEqual({
attributes: { name: 'Jane Doe', ownerId: '4', appRoles: ['2', '3'] },
});
});
it('supports specifying different relationship name and type', () => {
const relationships = {
ownerId: { name: 'owner', type: 'users' },
appRoles: { name: 'roles', type: 'app_roles' },
};
expect(buildPayload(item, relationships)).toEqual({
attributes: { name: 'Jane Doe' },
relationships: {
roles: {
data: [
{ type: 'app_roles', id: '2' },
{ type: 'app_roles', id: '3' },
],
},
owner: {
data: {
type: 'users',
id: '4',
},
},
},
});
});
it('defaults to use the attr as the relationship name if omitted', () => {
const object = { owner: '4' };
expect(buildPayload(object, { owner: { type: 'users' } })).toEqual({
relationships: {
owner: {
data: {
type: 'users',
id: '4',
},
},
},
});
});
it('extracts the id out of related objects', () => {
const object = {
appRoles: [{ id: '1', name: 'One' }, { id: '2', name: 'Two' }],
owner: { id: '3', name: 'Jane Doe' },
};
expect(
buildPayload(object, { appRoles: 'app_roles', owner: { type: 'users' } })
).toEqual({
relationships: {
app_roles: {
data: [
{ type: 'app_roles', id: '1' },
{ type: 'app_roles', id: '2' },
],
},
owner: {
data: {
type: 'users',
id: '3',
},
},
},
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment