'Best way to replace strings inside an JavaScript object

I have an unknown javascript object (let's call it IncompleteObject just for readability) and an array of IVariables which can be anything, but in the following format:

key: string
value: unknown

Example:

IVariables:

[
    { key: 'someObject', value: { some:'value' },
    { key: 'name', value: 'another value' },
    { key: 'lastName', value: 'this variable exists but wont be used' }
]

IncompleteObject:

{
    ID: "SGML",
    SortAs: "{{someObject}}",
    GlossTerm: "Standard Generalized Markup Language",
    Acronym: "The acronym is {{name}}",
    GlossSee: "markup"
}

Expected Result:

{
    ID: "SGML",
    SortAs: { 
        some:'value' 
    },
    GlossTerm: "Standard Generalized Markup Language",
    Acronym: "The acronym is another value",
    GlossSee: "markup"
}

The solution i thought was stringfying the object, replacing everything as strings and then trying to parse as JSON again (if it fails, it fails), but i'm wondering if there is a better solution to this... Also idk how to make it so that the SortAs for example becomes an object and not a string

Thanks!

*Notes:

  • The object won't necessarily have all variables, like the example.
  • The {{}} format is just an idea, i have no problem changing it since {} is used in JSON


Solution 1:[1]

I ended up doing with JSON.stringfy and some regex.

Here is how i did it:

const incompleteObject = {
    NoVariable: "Something",
    JustTheObject: "{{someObject}}",
    JustTheString: "{{name}}",
    IntegerValue: "{{integer}}",
    StringInsideOtherString: "Bla bla bla {{name}}",
    ObjectInsideString: "The acronym is {{name}}",
    VariableThatDoesntExist: "{{thisdoesntexist}}",
    ArrayToTestNestedStuff: [
        {
            JustTheObject: "{{someObject}}",
            JustTheString: "{{name}}"
        },
        {
            JustTheObject: "{{someObject}}",
            JustTheString: "{{name}}"
        }
    ]
}

const variablesToSubstitute: Record<string, unknown> = {
    someObject: { some:'value' },
    name: 'another value',
    integer: 2,
    lastName: 'this variable exists but wont be used' 
}
const result = replaceVariables(incompleteObject, variablesToSubstitute)
console.log(JSON.stringify(result, null, 2))
const replaceVariables = (objectToReplace: unknown, variables: Record<string, unknown>) => {
    let stringfiedObject = JSON.stringify(objectToReplace)
    stringfiedObject = replaceEntireProperty(stringfiedObject, variables)
    stringfiedObject = replaceSubstring(stringfiedObject, variables)
    const result = JSON.parse(stringfiedObject)
    return result
}
const replaceEntireProperty = (stringfiedObject: string, variables: Record<string, unknown>) => {
    stringfiedObject = stringfiedObject.replace(/"{{[\w]+}}"/g, (substring: string, ...args: any[]) => {
        const substringWithoutBracesAndComma = substring.substring(3, substring.length-3)
        return JSON.stringify(variables[substringWithoutBracesAndComma] ?? removeAllUnescapedCommas(substring))
    })
    return stringfiedObject
}
const replaceSubstring = (stringfiedObject: string, variables: Record<string, unknown>) => {
    stringfiedObject = stringfiedObject.replace(/{{[\w]+}}/g, (substring: string, ...args: any[]) => {
        const substringWithoutBraces = substring.substring(2, substring.length-2)
        return removeAllUnescapedCommas(JSON.stringify(variables[substringWithoutBraces] ?? substring))
    })
    return stringfiedObject
}
const removeAllUnescapedCommas = (stringToUnescape: string) => {
    return stringToUnescape.replace(/(?<!\\)\"/g, "")
} 

Here is also a GitHub repository with the solution working (Since its typescript, i didnt managed to add the code snippet): https://github.com/vceolin/replace-variables-inside-object

Solution 2:[2]

You won't be able add objects during replacing a string, so you'd need do the check if the tag is entire string or not beforehand:

const IVariables = [
    { key: 'someObject', value: { some:'value' }},
    { key: 'name', value: 'another value' },
    { key: 'lastName', value: 'this variable exists but wont be used' }
];
const incompleteObject = {
    ID: "SGML",
    SortAs: "{{someObject}}",
    GlossTerm: "Standard Generalized Markup Language",
    Acronym: "The acronym is {{name}}",
    GlossSee: "markup {{non-existing-tag}}",
    blah: "{{name}} and again {{name}}"
}

//convert array into object
const dataVariables = IVariables.reduce((a, b) => (a[b.key] = b.value, a), {});
const reg = /({{([^}]+)}})/;
for(let key in incompleteObject)
{
  const data = incompleteObject[key],
        variable = data.match(reg);

  if (!variable)
    continue;

  if (variable[1] == data) //if entire string a tag, don't use string replace
    incompleteObject[key] = dataVariables[variable[2]] || variable[1];
  else
    incompleteObject[key] = data.replace(new RegExp(reg, "g"), (a, b, c) => dataVariables[c] || b)
}

console.log(incompleteObject);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Vitor Ceolin
Solution 2