Skip to content

Instantly share code, notes, and snippets.

@turboza
Created September 20, 2016 13:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save turboza/d30675f1a309e6e8799f243dcf9c170c to your computer and use it in GitHub Desktop.
Save turboza/d30675f1a309e6e8799f243dcf9c170c to your computer and use it in GitHub Desktop.
Parameter serializer to create URL query string from nested objects.
/**
* =========================================
* paramSerialize
* =========================================
* Serialize JS object into URL Query string format.
*
* @param {object} params - { a: 10, filters: { x: 2, y: true } }
* @return {string} - '?a=10&filters[x]=2&filters[y]=true'
*/
export default function paramSerialize(params) {
if (! isObject(params)) {
console.warn(`params (${params}) must be an object`);
return '';
}
const queryArray = objectToQueryList(params);
return queryArray.length > 0 ? ('?' + queryArray.join('&')) : '';
}
/**
* Helper - Object to QueryString array
* -----------------------------------------
*
* Usage:
* objectToQueryList({ a: 10, b: 'hi' }) === ['a=10', 'b=hi']
* objectToQueryList({ a: 10, filters: { x: 2, y: true } }) === ['a:10', 'filters[x]=2', 'filters[y]=true']
* objectToQueryList({ filters: { extra: { m: 5 } } }) === ['filters[extra][m]=5']
*/
function objectToQueryList(object, parentKey) {
return Object.keys(object).reduce((prevList, key) => {
const value = object[key];
if (isInvalidValue(value)) {
return prevList;
}
const encodedKey = encodeURIComponent(key);
const fullKey = parentKey ? `${parentKey}[${encodedKey}]` : encodedKey;
if (isObjectOrArray(value)) {
return prevList.concat(objectToQueryList(value, fullKey));
}
const encodedValue = encodeURIComponent(value);
return prevList.concat(`${fullKey}=${encodedValue}`);
}, []);
}
/**
* Helper - isObject (Not Array)
* -----------------------------------------
*/
function isObject(input) {
return input && typeof(input) === 'object' && ! Array.isArray(input);
}
/**
* Helper - isInvalidValue
* -----------------------------------------
*/
function isInvalidValue(input) {
return typeof(input) === 'undefined' || input === null || input === '';
}
/**
* Helper - isObjectOrArray
* -----------------------------------------
*/
function isObjectOrArray(input) {
return input && typeof(input) === 'object';
}
import expect from 'expect';
import paramSerialize from 'src/helpers/paramSerialize.js';
describe('paramSerialize()', () => {
it('should return empty string for empty object', () => {
expect(paramSerialize({})).toEqual('');
});
it('should return empty string for invalid object', () => {
expect(paramSerialize('')).toEqual('');
expect(paramSerialize(1)).toEqual('');
expect(paramSerialize(1.123)).toEqual('');
expect(paramSerialize(null)).toEqual('');
expect(paramSerialize(true)).toEqual('');
expect(paramSerialize(false)).toEqual('');
expect(paramSerialize('test string')).toEqual('');
expect(paramSerialize(['abc', 123])).toEqual('');
expect(paramSerialize()).toEqual('');
});
it('should ignore undefine, null, blank, property', () => {
expect(paramSerialize({ limit: 20, offset: undefined })).toEqual('?limit=20');
expect(paramSerialize({ limit: 20, offset: null })).toEqual('?limit=20');
expect(paramSerialize({ limit: 20, offset: '' })).toEqual('?limit=20');
});
it('should return correct serialized string with edge cases, 0 or false', () => {
expect(paramSerialize({ a: false })).toEqual('?a=false');
expect(paramSerialize({ a: 0 })).toEqual('?a=0');
});
it('should return correct serialized string', () => {
expect(paramSerialize({ a: 1, b: 'hi' })).toEqual('?a=1&b=hi');
expect(paramSerialize({ a: 1, b: true })).toEqual('?a=1&b=true');
});
it('should encode string', () => {
expect(paramSerialize({ a:1, 'b boy':'hi nam' })).toEqual('?a=1&b%20boy=hi%20nam');
});
it('should encode 1 level of nested object', () => {
const params = { a: 1, filters: { amount: '1000..5000', paid: '1' } };
expect(paramSerialize(params))
.toEqual('?a=1&filters[amount]=1000..5000&filters[paid]=1');
});
it('should encode 2 levels of nested object', () => {
const params = {
a: 1,
filters: {
amount: '1000..5000', paid: '1', extra: { iin: '424242' },
},
};
expect(paramSerialize(params))
.toEqual('?a=1&filters[amount]=1000..5000&filters[paid]=1&filters[extra][iin]=424242');
});
it('should ignore empty object', () => {
expect(paramSerialize({ a: 10, filters: {} })).toEqual('?a=10');
expect(paramSerialize({ a: 10, filters: { b: 20, extra: {} } }))
.toEqual('?a=10&filters[b]=20');
});
it('should be able to handle array inside', () => {
expect(paramSerialize({ a: 10, filters: ['hi', 1] }))
.toEqual('?a=10&filters[0]=hi&filters[1]=1');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment