0

I have an object with data and a string that works as a template, the purpose is to map the object's data into the string's variables. This is an example of my string:


const menu = {
   breakfast: {
      description:'something' 
      }
   meal: {
      description: 'anotherSomething'
      }
}

const template = `<div>
   <ul>
     <li>
       %breakfast.description%
     </li>
     <li>
       %meal.description%
     </li>
   </ul>  
</div>
`

Obviously the simplest solution would be to create an array of the string variables and map the keys from my object, concatenate them and replace them like so:

const myVariablesFromTemplate = ['breakfast.description', 'meal.description']
const myKeysToMap = {}
Object.keys(menu).forEach((item)=>{
    Object.keys(menu[item]).forEach((subItem)=>{
       const myNewKey = `${item}.${subItem}`
       myKeysToMap[myNewKey] = menu[item][subItem]
    })
  })

for(let key in myVariablesFromTemplate ){
  template.replace(key, myKeysToMap[key])
}

But I'm looking for a more elegant solution, where I could use javascript in the string dynamically and just access the object like in a template literal but I don't know if that's even possible. My method is not very robust, I hope you guys can help!

Manuel Duarte
  • 644
  • 4
  • 18

2 Answers2

1

Use a regular expression replacement with a function as the replacement, so it can look up the value in the the object.

const menu = {
  breakfast: {
    description: 'something'
  },
  meal: {
    description: 'anotherSomething'
  }
}

const template = `<div>
   <ul>
     <li>
       %breakfast.description%
     </li>
     <li>
       %meal.description%
     </li>
   </ul>  
</div>
`

let result = template.replace(/%([^%]+)%/g, (g0, g1) => lookup(g1, menu));

console.log(result);

function lookup(path, obj) {
  return path.split('.').reduce((p, c) => p && p[c] || null, obj);
}

I got the lookup() function from this answer.

Barmar
  • 741,623
  • 53
  • 500
  • 612
0

I found a solution that works in which I can replace my template variables with embedded template literals and then evaluate the whole template string as javascript. It's important to destructure the myMenuInformation object so the eval function has access to those values, otherwise it will return an error or undefined. I used this question as reference alongside the mdn web docs:

Can ES6 template literals be substituted at runtime (or reused)?

MDN Web Docs Eval Function

function replaceVariables(myTemplate, myMenuInformation){
   const matchings = myTemplate.match(/(?<=\%)(.*?)(?=\%)/g);
    
        if (!matchings) {
          return myTemplate;
        }
        const originalMatchingText = matchings.map((m) => ({
          regexForReplacement: new RegExp('\\%' + m + '%', 'g'),
          templateLiteralVar: '${'+m+'}',
        }));
        
        let newBody = myTemplate;
        originalMatchingText.forEach((match) => {
          newBody = newBody.replace(
            match.regexForReplacement,
            match.templateLiteralVar,
          );
        });

        newBody = '`' + newBody + '`'
        var {breakfast, meal} = myMenuInformation
        return eval(newBody)
}

const mapped = replaceVariables(template, menu)
console.log(mapped)

// RESULT:
//<div>
//   <ul>
//     <li>
//     something
//     </li>
//     <li>
//       anotherSomething
//     </li>
//   </ul>  
//</div>
Manuel Duarte
  • 644
  • 4
  • 18