I want to create a javascript object from a template. The problem is I don't know what the template is going to look like beforehand. As a simple example, if I had the template function
template = function (data) {
return {
title: data.title
}
}
then I could run template({ title: "Steve" })
and get back the object
{ title: "Steve" }
Because data.title
is not evaluated until I call the template function. But I'm constructing an object based on user input where the field names are not known beforehand and could be deeply nested anywhere in the object.
If I define the object that is returned beforehand then the data.title
field in the example would already be evaluated and wouldn't use the input data. For example, I want to be able to define the template object like
obj = { title: this.title }
then redefine the template as
template = function () {
return obj
}
and call template.call({title:"Steve"})
. But currently I get back
{ title: undefined }
because this.title
was already evaluated when I defined obj
. Maybe I'm approaching this the wrong way, because I keep coming to the conclusion that I'd have to modify the function by stringifying it, modifying the string to include the unevaluated code this.title
and creating a new function from the string. But that seems like a plain awful idea.
And traversing the object looking for special values to replace seems expensive and complicated. I also looked for some sort of javascript object templating library but didn't find anything.
EDIT: To make it more clear that the input data and the template structure won't necessarily match, I may want have a template that looks like
template = function (data) {
return {
name: "Alfred",
stats: {
age: 32,
position: {
level: 10,
title: data.title
}
}
}
}
and call template({title:"Manager"})
to get
{ "name": "Alfred", "stats": { "age": 32, "position": { "level": 10, "title": "Manager" } } }
I want to create a javascript object from a template. The problem is I don't know what the template is going to look like beforehand. As a simple example, if I had the template function
template = function (data) {
return {
title: data.title
}
}
then I could run template({ title: "Steve" })
and get back the object
{ title: "Steve" }
Because data.title
is not evaluated until I call the template function. But I'm constructing an object based on user input where the field names are not known beforehand and could be deeply nested anywhere in the object.
If I define the object that is returned beforehand then the data.title
field in the example would already be evaluated and wouldn't use the input data. For example, I want to be able to define the template object like
obj = { title: this.title }
then redefine the template as
template = function () {
return obj
}
and call template.call({title:"Steve"})
. But currently I get back
{ title: undefined }
because this.title
was already evaluated when I defined obj
. Maybe I'm approaching this the wrong way, because I keep coming to the conclusion that I'd have to modify the function by stringifying it, modifying the string to include the unevaluated code this.title
and creating a new function from the string. But that seems like a plain awful idea.
And traversing the object looking for special values to replace seems expensive and complicated. I also looked for some sort of javascript object templating library but didn't find anything.
EDIT: To make it more clear that the input data and the template structure won't necessarily match, I may want have a template that looks like
template = function (data) {
return {
name: "Alfred",
stats: {
age: 32,
position: {
level: 10,
title: data.title
}
}
}
}
and call template({title:"Manager"})
to get
{ "name": "Alfred", "stats": { "age": 32, "position": { "level": 10, "title": "Manager" } } }
Share Improve this question edited Apr 21, 2014 at 9:25 Drew Greene asked Apr 21, 2014 at 9:15 Drew GreeneDrew Greene 2911 gold badge3 silver badges12 bronze badges 2- api.jquery.com/jquery.extend – setec Commented Apr 21, 2014 at 9:16
- Thanks but maybe I should've been more clear: the problem with this is that the input data will not match the structure of the template. The input values may have to be placed in a deeply nested part of the object or multiple times within the same object. I won't "know" what it looks like when I call the template function. – Drew Greene Commented Apr 21, 2014 at 9:19
3 Answers
Reset to default 11So I've managed to solve this by (ab)using functions as metadata to mark the values that should be replaced in the template. This is made possible by two things:
- I only need valid JSON values, so I can safely say that functions aren't literal user input
- JSON.stringify has a replacer parameter which will traverse the object and can be used to pass the input data to the template
Using a template generator like this
var templateMaker = function (object) {
return function (context) {
var replacer = function (key, val) {
if (typeof val === 'function') {
return context[val()]
}
return val;
}
return JSON.parse(JSON.stringify(obj, replacer))
}
}
I create a template object, replacing field names with functions that return the field name
var obj = {
name: "Alfred",
stats: {
age: 32,
position: {
title: function () { return 'title' },
level: function () { return 'level' }
}
}
}
then I create the template function, define my input, and render it to an object
var template = templateMaker(obj);
var data = {
title: "Manager",
level: 10
}
var rendered = template(data);
and magically, the object output looks like
{
"name": "Alfred",
"stats": {
"age": 32,
"position": {
"title": "Manager",
"level": 10
}
}
}
Maybe template engines like Mustache would help you with this.
You can define your object template in string:
var template = '{ title: {{title}} }';
then render it with the data, and convert it to json:
var data = {title: 'I am title'};
var obj = JSON.parse(Mustache.render(template, data));
UPDATE:
I read your updated example, here is the corresponding example:
var template = JSON.stringify({
name: "Alfred",
stats: {
age: 32,
position: {
level: 10,
title: '{{title}}'
}
}
});
var data = {title: 'I am title'};
var obj = JSON.parse(Mustache.render(template, data));
obj.stats.position.title == "I am title";
If you don't want to include a 3rd party rendering library, you can (ab)use template literals and function scopes:
const renderTemplate = (template, params) => {
const names = Object.keys(params)
const vals = Object.values(params)
return new Function(...names, `return \`${template}\``)(...vals)
}
And then you can have templates that are still valid json, but with dynamically replaced values:
const jsonString = '{"title":"${title}"}'
const renderedJson = renderTemplate(
jsonString,
{
title: "Dynamic value"
}
)