UNPKG

json-placeholder-replacer

Version:

Javascript/Typescript library/cli to replace placeholders in an javascript object

203 lines (202 loc) 8.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonPlaceholderReplacer = void 0; const defaultDelimiterTags = [ { begin: "{{", end: "}}", }, { begin: "<<", end: ">>", }, ]; const defaultSeparator = ":"; const defaultNullishValues = []; class JsonPlaceholderReplacer { constructor(options) { this.variablesMap = []; this.stringifyCache = new WeakMap(); this.escapeRegExp = (text) => text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); this.configuration = this.initializeConfigurations(options); this.serializedNullishValues = new Set(this.configuration.nullishValues.map((v) => JSON.stringify(v))); const delimiterTagsRegexes = this.configuration.placeholders .map((delimiterTag) => `^${delimiterTag.escapedBeginning}[^${delimiterTag.escapedEnding}]+${delimiterTag.escapedEnding}$`) .join("|"); this.delimiterTagsRegex = new RegExp(delimiterTagsRegexes); } addVariableMap(variableMap) { if (typeof variableMap === "string") { try { this.variablesMap.unshift(JSON.parse(variableMap)); } catch (error) { throw new Error(`Invalid JSON string provided to addVariableMap: ${error instanceof Error ? error.message : "Unknown error"}`); } } else { this.variablesMap.unshift(variableMap); } return this; } setVariableMap(...variablesMap) { this.variablesMap.length = 0; variablesMap.forEach((variableMap) => this.addVariableMap(variableMap)); return this; } replace(json) { return this.replaceChildren(json, this.variablesMap, new WeakSet()); } replaceWith(json, ...variablesMap) { return this.replaceChildren(json, variablesMap.reverse(), new WeakSet()); } initializeConfigurations(options) { var _a; const tags = (options === null || options === void 0 ? void 0 : options.delimiterTags) || defaultDelimiterTags; const delimiterTags = tags.map((tag) => { const escapedBeginning = this.escapeRegExp(tag.begin); const escapedEnding = this.escapeRegExp(tag.end); return { begin: tag.begin, end: tag.end, escapedBeginning, escapedEnding, regex: new RegExp(`(${escapedBeginning}[^${escapedEnding}]+${escapedEnding})`, "g"), }; }); let defaultValueSeparator = defaultSeparator; if ((options === null || options === void 0 ? void 0 : options.defaultValueSeparator) !== undefined) { defaultValueSeparator = options.defaultValueSeparator; } let nullishValues = defaultNullishValues; if ((_a = options === null || options === void 0 ? void 0 : options.nullishValues) === null || _a === void 0 ? void 0 : _a.length) { nullishValues = options.nullishValues; } return { defaultValueSeparator, placeholders: delimiterTags, nullishValues, }; } replaceChildren(node, variablesMap, visited) { if (typeof node === "object" && node !== null) { if (visited.has(node)) { return node; } visited.add(node); for (const key in node) { const attribute = node[key]; if (typeof attribute === "object" && attribute !== null) { node[key] = this.replaceChildren(attribute, variablesMap, visited); } else if (attribute === null) { node[key] = null; } else if (attribute !== undefined) { node[key] = this.replaceValue(attribute, variablesMap); } } return node; } else if (node !== undefined) { return this.replaceValue(node, variablesMap); } return node; } replaceValue(node, variablesMap) { const attributeAsString = node.toString(); const placeHolderIsInsideStringContext = !this.delimiterTagsRegex.test(node); // Create the replacer function once per call const replacerFn = this.replacer(placeHolderIsInsideStringContext, variablesMap); const output = this.configuration.placeholders.reduce((acc, delimiterTag) => { return acc.replace(delimiterTag.regex, replacerFn(delimiterTag)); }, attributeAsString); if (output === attributeAsString) { return node; } // Only try JSON.parse if the output looks like JSON and is not inside string context if (!placeHolderIsInsideStringContext) { try { return JSON.parse(output); } catch (exc) { // If JSON parsing fails, return the output as-is return output; } } return output; } replacer(placeHolderIsInsideStringContext, variablesMap) { return (delimiterTag) => (placeHolder) => { const { tag, defaultValue } = this.parseTag(placeHolder, delimiterTag); const mapCheckResult = this.checkInEveryMap(tag, variablesMap); if (mapCheckResult === undefined) { if (defaultValue !== undefined) { return defaultValue; } return placeHolder; } if (!placeHolderIsInsideStringContext) { // For non-string context, we'll return JSON.stringify and let replaceValue handle the parsing return this.safeStringify(mapCheckResult); } if (typeof mapCheckResult === "object" && mapCheckResult !== null) { return this.safeStringify(mapCheckResult); } return String(mapCheckResult); }; } safeStringify(value) { if (typeof value === "object" && value !== null) { if (this.stringifyCache.has(value)) { return this.stringifyCache.get(value); } const stringified = JSON.stringify(value); this.stringifyCache.set(value, stringified); return stringified; } return JSON.stringify(value); } parseTag(placeHolder, delimiterTag) { const path = placeHolder.substring(delimiterTag.begin.length, placeHolder.length - delimiterTag.begin.length); let tag = path; let defaultValue; const defaultValueSeparatorIndex = path.indexOf(this.configuration.defaultValueSeparator); if (defaultValueSeparatorIndex > 0) { tag = path.substring(0, defaultValueSeparatorIndex); defaultValue = path.substring(defaultValueSeparatorIndex + this.configuration.defaultValueSeparator.length); } return { tag, defaultValue }; } checkInEveryMap(path, variablesMap) { for (const map of variablesMap) { let value = this.navigateThroughMap(map, path); if (value !== undefined && !this.isNullishValue(value)) { return value; } } return undefined; } isNullishValue(value) { if (this.serializedNullishValues.size === 0) { return false; } const stringified = this.safeStringify(value); return this.serializedNullishValues.has(stringified); } navigateThroughMap(map, path) { if (map === undefined) { return; } const shortCircuit = map[path]; if (shortCircuit !== undefined) { return shortCircuit; } const keys = path.split("."); const key = keys[0]; keys.shift(); return this.navigateThroughMap(map[key], keys.join(".")); } } exports.JsonPlaceholderReplacer = JsonPlaceholderReplacer;