@clynn-fe/akfe-editor-stringify-object
Version:
stringify runtime js object
211 lines (173 loc) • 4.96 kB
text/typescript
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