UNPKG

@clynn-fe/akfe-editor-stringify-object

Version:

stringify runtime js object

211 lines (173 loc) 4.96 kB
import isRegexp from 'is-regexp' import { isArrowFunction } from '@clynn-fe/akfe-editor-utils' import { isObj } from './utils' import getOwnEnumPropSymbols from 'get-own-enumerable-property-symbols' type TOptions = { indent?: string singleQuotes?: boolean filter?: <T, K extends keyof T>(object: T, property: K) => boolean transform?: <T, K extends keyof T>( object: T, property: K, originalResult: string ) => string inlineCharacterLimit?: number } interface ITokens { newLine: string newLineOrSpace: string pad: string indent: string } const seen: unknown[] = [] const stringifyObject = ( input: unknown, options: TOptions = {}, pad = '' ): string => { options.indent = options.indent || '\t' let tokens: ITokens if (options.inlineCharacterLimit === undefined) { tokens = { newLine: '\n', newLineOrSpace: '\n', pad, indent: pad + options.indent } } else { tokens = { newLine: '@@__STRINGIFY_OBJECT_NEW_LINE__@@', newLineOrSpace: '@@__STRINGIFY_OBJECT_NEW_LINE_OR_SPACE__@@', pad: '@@__STRINGIFY_OBJECT_PAD__@@', indent: '@@__STRINGIFY_OBJECT_INDENT__@@' } } const expandWhiteSpace = (string: string) => { if (options.inlineCharacterLimit === undefined) { return string } const oneLined = string .replace(new RegExp(tokens.newLine, 'g'), '') .replace(new RegExp(tokens.newLineOrSpace, 'g'), ' ') .replace(new RegExp(tokens.pad + '|' + tokens.indent, 'g'), '') if (oneLined.length <= options.inlineCharacterLimit) { return oneLined } return string .replace( new RegExp(tokens.newLine + '|' + tokens.newLineOrSpace, 'g'), '\n' ) .replace(new RegExp(tokens.pad, 'g'), pad) .replace(new RegExp(tokens.indent, 'g'), pad + options.indent) } if (seen.indexOf(input) !== -1) { return '"[Circular]"' } if ( input === null || input === undefined || typeof input === 'number' || typeof input === 'boolean' || typeof input === 'function' || typeof input === 'symbol' || isRegexp(input) ) { return String(input) } if (input instanceof Date) { return `new Date('${input.toISOString()}')` } if (input instanceof Map) { return `new Map(${stringifyObject([...input.entries()], options, pad)})` } if (input instanceof Set) { return `new Set(${stringifyObject([...input.values()], options, pad)})` } if (Array.isArray(input)) { if (input.length === 0) { return '[]' } seen.push(input) const ret = '[' + tokens.newLine + input .map((el, i) => { const eol = input.length - 1 === i ? tokens.newLine : ',' + tokens.newLineOrSpace let value = stringifyObject(el, options, pad + options.indent) if (options.transform) { value = options.transform(input, i, value) } return tokens.indent + value + eol }) .join('') + tokens.pad + ']' seen.pop() return expandWhiteSpace(ret) } if (isObj(input)) { let objKeys = [...Object.keys(input), ...getOwnEnumPropSymbols(input)] if (options.filter) { objKeys = objKeys.filter(el => // eslint-disable-next-line @typescript-eslint/no-non-null-assertion options.filter!(input, el) ) } if (objKeys.length === 0) { return '{}' } seen.push(input) const ret = '{' + tokens.newLine + objKeys .map((el, i) => { const eol = objKeys.length - 1 === i ? tokens.newLine : ',' + tokens.newLineOrSpace const isClassic = typeof el !== 'symbol' && /^[a-z$_][a-z$_0-9]*$/i.test(el) const key = typeof el === 'symbol' || isClassic ? el : stringifyObject(el, options) let value = stringifyObject( (input as any)[el], options, pad + options.indent ) const isMethod = typeof (input as any)[el] === 'function' && !isArrowFunction(value) && !/^(function\b|\()/.test(value) && !/^(async function\b|\()/.test(value) if (options.transform) { value = options.transform(input, el, value) } return ( tokens.indent + (isMethod ? '' : String(key) + ': ') + value + eol ) }) .join('') + tokens.pad + '}' seen.pop() return expandWhiteSpace(ret) } let result = String(input).replace(/[\r\n]/g, x => x === '\n' ? '\\n' : '\\r' ) if (options.singleQuotes === false) { result = result.replace(/"/g, '\\"') return `"${result}"` } result = result.replace(/\\?'/g, "\\'") return `'${result}'` } export default stringifyObject