Skip to the code
cf.

Template renderer

Write render(template, data). It returns template with every {{ path }} placeholder replaced by the value at that path in data.

A path is one or more keys joined by dots, like name, user.name, or items.0. A key uses only the characters A to Z, a to z, 0 to 9, _, and $. Any amount of whitespace may sit between the braces and the path, so {{name}} and {{ name }} are both placeholders.

Follow the path one key at a time. If a step along the way is null or undefined, or the final value is, the placeholder becomes an empty string. Any other value goes through String, so 0 and false show as 0 and false.

Insert each value as plain text: a value that contains {{ b }} or $& shows up exactly as it is and is never searched for placeholders. Leave everything that isn't a placeholder alone, including single braces and braces around anything that isn't a path, such as {{ }} or {{ a b }}.

render("Hi {{ user.name }}, you have {{count}} new messages.", {
  user: { name: "Ada" },
  count: 0,
});
// "Hi Ada, you have 0 new messages."

What the tests check

  1. replaces a placeholder with its value
    expect(render("Hello, {{name}}!", { name: "Ada" })).toBe("Hello, Ada!");
    
  2. allows whitespace inside the braces
    expect(render("{{ a }}-{{  b}}-{{c\t}}", { a: 1, b: 2, c: 3 })).toBe("1-2-3");
    
  3. replaces every placeholder, including repeats
    expect(render("{{x}}{{ x }}, {{y}}{{x}}", { x: "a", y: "b" })).toBe("aa, ba");
    
  4. follows dotted paths into objects and arrays
    const data = { user: { address: { city: "Paris" } }, items: ["x", "y"] };
    expect(render("{{ user.address.city }} / {{ items.1 }}", data)).toBe("Paris / y");
    
  5. renders missing, null, and undefined values as empty
    const data = { a: null, b: undefined, g: {} };
    expect(render("[{{ a }}][{{ b }}][{{ c }}][{{ d.e.f }}][{{ g.h }}]", data)).toBe("[][][][][]");
    
  6. shows 0, false, and other values with String
    expect(render("{{ n }} {{ ok }} {{ list }}", { n: 0, ok: false, list: [1, 2] })).toBe("0 false 1,2");
    
  7. inserts values as plain text
    expect(render("{{ a }} | {{ b }}", { a: "{{ b }}", b: "$& costs $1" })).toBe("{{ b }} | $& costs $1");
    
  8. leaves everything that isn't a placeholder alone
    const template = "{a} {{ }} {{ a b }} {{a} function () { return {}; }";
    expect(render(template, { a: 1 })).toBe(template);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).