json-placeholder-replacer
Version:
Javascript/Typescript library/cli to replace placeholders in an javascript object
203 lines (202 loc) • 8.03 kB
JavaScript
"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;