import { jsonEscapes, reEscapedJson, reHasEscapedJson } from "./_internal/_global"; import isArray from "./lodash/basic/isArray"; import forEach from "./lodash/#forEach"; import forOwn from "./lodash/#forOwn"; import has from "./lodash/#has"; import isDate from "./lodash/#isDate"; import isString from "./lodash/#isString"; function concatJson(head: string, partial: Array, gap: string, mind: string, tail: string): string { return gap ? head + "\n" + gap + partial.join(",\n" + gap) + "\n" + mind + tail : head + partial.join(",") + tail; } function concatJsonKey(string: string): string { return reHasEscapedJson.test(string) ? '"' + escapeJsonKey(string) + '"' : '"' + string + '"'; } function concatSpaceIndent(n: number): string { let indent = "", index = -1; while (++index < n) { indent += " "; } return indent; } function escapeJsonKey(string: string): string { return string.replace(reEscapedJson, (matched) => { const escaped = has(jsonEscapes, matched) ? jsonEscapes[matched as JsonEscape] : undefined; return isString(escaped) ? escaped : hexEncode(matched); }); } function getPrimitiveValue(value: any): any { return isDate(value) ? value.toString() : value.valueOf(); } function hexEncode(string: string): string { return "\\u" + ("0000" + string.charCodeAt(0).toString(16)).slice(-4); } /** * 字符串化,同 JSON.stringify。 * * @param {*} value * @param {(number | string)} [indent=4] * @returns {string} * @since 0.1.0 * @category Soil * @see {@linkcode parseJson} * @example * * ```ts * _.log(_.stringify(app.effects)); * // 结果:桌面日志会以标准 Json 格式记录当前 Ae 中的所有效果属性。 * ``` */ function stringify(value: any, indent: number | string = 4): string { return stringifyValue(value, isString(indent) ? indent : concatSpaceIndent(indent), ""); } function stringifyArray(array: Array, indent: string, gap: string): string { const mind = gap; gap += indent; let partial: Array = []; forEach(array, (value, index) => { partial[index] = stringifyValue(value, indent, gap); }); return partial.length === 0 ? "[]" : concatJson("[", partial, gap, mind, "]"); } function stringifyObject(object: AnyObject, indent: string, gap: string): string { const mind = gap; gap += indent; const colon = gap ? ": " : ":"; let partial: Array = []; forOwn(object, (value, key) => { partial.push(concatJsonKey(key) + colon + stringifyValue(value, indent, gap)); }); return partial.length === 0 ? "{}" : concatJson("{", partial, gap, mind, "}"); } function stringifyValue(value: any, indent: string, gap: string): string { if (value == null) { return "null"; } const primitive = getPrimitiveValue(value); switch (typeof primitive) { case "string": return concatJsonKey(primitive); case "number": return isFinite(primitive) ? String(primitive) : "null"; case "boolean": return String(primitive); case "object": return isArray(primitive) ? stringifyArray(primitive, indent, gap) : stringifyObject(primitive, indent, gap); case "function": return '"' + escapeJsonKey(primitive.toString()) + '"'; default: return "null"; } } export default stringify;