Last active
March 9, 2023 22:07
-
-
Save kibolho/af0dbaf2c780a2165a186991e7159493 to your computer and use it in GitHub Desktop.
Stringfy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* 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; | |
| arrayFormat?: "brackets" | "comma" | "indices" | "repeat"; | |
| } | |
| export const stringifyObject = (object: any, opts: StringifyOptions = { | |
| encodeValuesOnly: true, | |
| encode: false, | |
| encoder: encodeURIComponent, | |
| addQueryPrefix: true, | |
| }): 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 && 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('✓'), 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 : ''; | |
| }; | |
| 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