Skip to content

Instantly share code, notes, and snippets.

@kibolho
Last active November 22, 2022 17:43
Show Gist options
  • Select an option

  • Save kibolho/19f1cce6767be7b635e21af225bb4c74 to your computer and use it in GitHub Desktop.

Select an option

Save kibolho/19f1cce6767be7b635e21af225bb4c74 to your computer and use it in GitHub Desktop.
Stringify Objects and Arrays into Query Params
import stringify from './stringfy';
const params = [{"teste": "1"},{"teste": "2"},{"teste": "3"}];
const stringParams = stringify(params, {
encodeValuesOnly: true,
encode: false,
encoder: encodeURIComponent,
addQueryPrefix: true,
});
/* eslint-disable no-bitwise */
interface StringifyOptions {
encoder?: any;
encode?: boolean;
encodeValuesOnly?: boolean;
addQueryPrefix?: boolean;
allowDots?: boolean;
charset?: string;
charsetSentinel?: boolean;
delimiter?: string;
skipNulls?: boolean;
strictNullHandling?: boolean;
}
const stringifyObject = (object: any, opts: any): string => {
let obj = object;
const options = normalizeStringifyOptions(opts);
let objKeys;
let filter;
if (typeof options.filter === 'function') {
filter = options.filter;
obj = filter('', obj);
} else if (isArray(options.filter)) {
filter = options.filter;
objKeys = filter;
}
const keys: any = [];
if (typeof obj !== 'object' || obj === null) {
return '';
}
let arrayFormat;
if (opts && opts.arrayFormat in arrayPrefixGenerators) {
arrayFormat = opts.arrayFormat;
} else if (opts && 'indices' in opts) {
arrayFormat = opts.indices ? 'indices' : 'repeat';
} else {
arrayFormat = 'indices';
}
const generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
if (!objKeys) {
objKeys = Object.keys(obj);
}
if (options.sort) {
objKeys.sort(options.sort);
}
for (let i = 0; i < objKeys.length; ++i) {
const key = objKeys[i];
if (options.skipNulls && obj[key] === null) {
continue;
}
pushToArray(
keys,
stringify({
object: obj[key],
prefix: key,
generateArrayPrefix: generateArrayPrefix,
strictNullHandling: options.strictNullHandling,
skipNulls: options.skipNulls,
encoder: options.encode ? options.encoder : null,
filter: options.filter,
sort: options.sort,
allowDots: options.allowDots,
serializeDate: options.serializeDate,
format: options.format,
formatter: options.formatter,
encodeValuesOnly: options.encodeValuesOnly,
charset: options.charset,
}),
);
}
const joined = keys.join(options.delimiter);
let prefix = options.addQueryPrefix === true ? '?' : '';
if (options.charsetSentinel) {
if (options.charset === 'iso-8859-1') {
// encodeURIComponent('&#10003;'), the "numeric entity" representation of a checkmark
prefix += 'utf8=%26%2310003%3B&';
} else {
// encodeURIComponent('✓')
prefix += 'utf8=%E2%9C%93&';
}
}
return joined.length > 0 ? prefix + joined : '';
};
export default (params: any, opts: StringifyOptions) =>
stringifyObject(params, opts);
const utils = {
hexTable: (() => {
const array = [];
for (let i = 0; i < 256; ++i) {
array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());
}
return array;
})(),
encode(str: any, defaultEncoder: any, charset: any, kind: any, format: any) {
// This code was originally written by Brian White (mscdex) for the io.js core querystring library.
// It has been adapted here for stricter adherence to RFC 3986
if (str.length === 0) {
return str;
}
let string = str;
if (typeof str === 'symbol') {
string = Symbol.prototype.toString.call(str);
} else if (typeof str !== 'string') {
string = String(str);
}
if (charset === 'iso-8859-1') {
return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
});
}
let out = '';
for (let i = 0; i < string.length; ++i) {
let c = string.charCodeAt(i);
if (
c === 0x2d || // -
c === 0x2e || // .
c === 0x5f || // _
c === 0x7e || // ~
(c >= 0x30 && c <= 0x39) || // 0-9
(c >= 0x41 && c <= 0x5a) || // a-z
(c >= 0x61 && c <= 0x7a) || // A-Z
(format === 'RFC1738' && (c === 0x28 || c === 0x29)) // ( )
) {
out += string.charAt(i);
continue;
}
if (c < 0x80) {
out = out + this.hexTable[c];
continue;
}
if (c < 0x800) {
out =
out +
(this.hexTable[0xc0 | (c >> 6)] + this.hexTable[0x80 | (c & 0x3f)]);
continue;
}
if (c < 0xd800 || c >= 0xe000) {
out =
out +
(this.hexTable[0xe0 | (c >> 12)] +
this.hexTable[0x80 | ((c >> 6) & 0x3f)] +
this.hexTable[0x80 | (c & 0x3f)]);
continue;
}
i += 1;
c = 0x10000 + (((c & 0x3ff) << 10) | (string.charCodeAt(i) & 0x3ff));
out +=
this.hexTable[0xf0 | (c >> 18)] +
this.hexTable[0x80 | ((c >> 12) & 0x3f)] +
this.hexTable[0x80 | ((c >> 6) & 0x3f)] +
this.hexTable[0x80 | (c & 0x3f)];
}
return out;
},
maybeMap: (val: any, fn: any) => {
if (isArray(val)) {
const mapped = [];
for (let i = 0; i < val.length; i += 1) {
mapped.push(fn(val[i]));
}
return mapped;
}
return fn(val);
},
isBuffer: (obj: any) => {
if (!obj || typeof obj !== 'object') {
return false;
}
return !!(
obj.constructor &&
obj.constructor.isBuffer &&
obj.constructor.isBuffer(obj)
);
},
};
const formatterFunction = (value: any) => String(value);
const arrayPrefixGenerators: any = {
brackets: function brackets(prefix: any) {
return prefix + '[]';
},
comma: 'comma',
indices: function indices(prefix: any, key: any) {
return prefix + '[' + key + ']';
},
repeat: function repeat(prefix: any) {
return prefix;
},
};
const isArray = Array.isArray;
const push = Array.prototype.push;
const pushToArray = function (arr: any[], valueOrArray: any[]) {
push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
};
const toISO = Date.prototype.toISOString;
const defaultFormat = 'RFC3986';
const defaults = {
addQueryPrefix: false,
allowDots: false,
charset: 'utf-8',
charsetSentinel: false,
delimiter: '&',
encode: false,
encoder: utils.encode,
encodeValuesOnly: false,
format: defaultFormat,
formatter: formatterFunction,
// deprecatedÎ
indices: false,
serializeDate: function serializeDate(date: any) {
return toISO.call(date);
},
skipNulls: false,
strictNullHandling: false,
};
const isNonNullishPrimitive = function isNonNullishPrimitive(v: any) {
return (
typeof v === 'string' ||
typeof v === 'number' ||
typeof v === 'boolean' ||
typeof v === 'symbol' ||
typeof v === 'bigint'
);
};
const stringify = ({
object,
prefix,
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate,
format,
formatter,
encodeValuesOnly,
charset,
}: any) => {
let obj = object;
if (typeof filter === 'function') {
obj = filter(prefix, obj);
} else if (obj instanceof Date) {
obj = serializeDate(obj);
} else if (generateArrayPrefix === 'comma' && isArray(obj)) {
obj = utils.maybeMap(obj, function (value: any) {
if (value instanceof Date) {
return serializeDate(value);
}
return value;
});
}
if (obj === null) {
if (strictNullHandling) {
return encoder && !encodeValuesOnly
? encoder(prefix, defaults.encoder, charset, 'key', format)
: prefix;
}
obj = '';
}
if (isNonNullishPrimitive(obj) || utils.isBuffer(obj)) {
if (encoder) {
const keyValue = encodeValuesOnly
? prefix
: encoder(prefix, defaults.encoder, charset, 'key', format);
return [
formatter(keyValue) +
'=' +
formatter(encoder(obj, defaults.encoder, charset, 'value', format)),
];
}
return [formatter(prefix) + '=' + formatter(String(obj))];
}
const values: any = [];
if (typeof obj === 'undefined') {
return values;
}
let objKeys;
if (generateArrayPrefix === 'comma' && isArray(obj)) {
// we need to join elements in
objKeys = [{ value: obj.length > 0 ? obj.join(',') || null : undefined }];
} else if (isArray(filter)) {
objKeys = filter;
} else {
const keys = Object.keys(obj);
objKeys = sort ? keys.sort(sort) : keys;
}
for (let i = 0; i < objKeys.length; ++i) {
const key = objKeys[i];
const value =
typeof key === 'object' && key.value !== undefined ? key.value : obj[key];
if (skipNulls && value === null) {
continue;
}
const keyPrefix = isArray(obj)
? typeof generateArrayPrefix === 'function'
? generateArrayPrefix(prefix, key)
: prefix
: prefix + (allowDots ? '.' + key : '[' + key + ']');
pushToArray(
values,
stringify({
object: value,
prefix: keyPrefix,
generateArrayPrefix,
strictNullHandling,
skipNulls,
encoder,
filter,
sort,
allowDots,
serializeDate,
format,
formatter,
encodeValuesOnly,
charset,
}),
);
}
return values;
};
const normalizeStringifyOptions = (opts: any): any => {
if (!opts) {
return defaults;
}
if (
opts.encoder !== null &&
opts.encoder !== undefined &&
typeof opts.encoder !== 'function'
) {
throw new TypeError('Encoder has to be a function.');
}
const charset = opts.charset || defaults.charset;
if (
typeof opts.charset !== 'undefined' &&
opts.charset !== 'utf-8' &&
opts.charset !== 'iso-8859-1'
) {
throw new TypeError(
'The charset option must be either utf-8, iso-8859-1, or undefined',
);
}
let format = 'RFC3986';
if (typeof opts.format !== 'undefined') {
format = opts.format;
}
const formatter = formatterFunction;
let filter;
if (typeof opts.filter === 'function' || isArray(opts.filter)) {
filter = opts.filter;
}
return {
addQueryPrefix:
typeof opts.addQueryPrefix === 'boolean'
? opts.addQueryPrefix
: defaults.addQueryPrefix,
allowDots:
typeof opts.allowDots === 'undefined'
? defaults.allowDots
: !!opts.allowDots,
charset: charset,
charsetSentinel:
typeof opts.charsetSentinel === 'boolean'
? opts.charsetSentinel
: defaults.charsetSentinel,
delimiter:
typeof opts.delimiter === 'undefined'
? defaults.delimiter
: opts.delimiter,
encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode,
encoder:
typeof opts.encoder === 'function' ? opts.encoder : defaults.encoder,
encodeValuesOnly:
typeof opts.encodeValuesOnly === 'boolean'
? opts.encodeValuesOnly
: defaults.encodeValuesOnly,
filter: filter,
format: format,
formatter: formatter,
serializeDate:
typeof opts.serializeDate === 'function'
? opts.serializeDate
: defaults.serializeDate,
skipNulls:
typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
sort: typeof opts.sort === 'function' ? opts.sort : null,
strictNullHandling:
typeof opts.strictNullHandling === 'boolean'
? opts.strictNullHandling
: defaults.strictNullHandling,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment