我有一个由数组组成的json对象,并映射了一些占位符文本,可以通过传递另一个对象来替换它们。
例如。
data = {
"name": "Hello ${user.name}",
"primary_task": "Task Name: ${user.tasks[0].name}",
"secondary_tasks": ["Task 2: ${user.tasks[1].name}", "Task 2: ${user.tasks[2].name}"]
}
变量或元数据对象可以
variables = {
"user": {
"name": "DJ"
},
"tasks": [
{
"name": "Task One"
}
]
}
我有一个函数,可以根据某个对象取字符串并进行替换。我不确定如何递归地在JSON对象上调用它,以便它可以替换map和array中的所有字符串值
var data = {
"name": "Hello ${user.name}",
"primary_task": "Task Name: Task One",
"secondary_tasks": ["Task 2: ${user.tasks[1].name}", "Task 2: ${user.tasks[2].name}"]
}
var metadata = {
"user": {
"name": "DJ",
"tasks": [
{
"name": "Task One"
}
],
},
}
function subString(str) {
var rxp = /\{([^}]+)\}/g,
liveStr = str,
curMatch;
while( curMatch = rxp.exec( str ) ) {
var match = curMatch[1];
liveStr = liveStr.replace("${"+ match + "}", tryEval(match));
}
return liveStr;
}
function tryEval(evalStr) {
evalStr = "metadata." + evalStr;
try {
return eval(evalStr);
}
catch(error) {
return "${" + evalStr + "}";
}
}
var str = "user ${user.name} - ${user.tasks[0].name} - ${user.tasks[2].name}";
console.log("Sub " + subString(str));
在上面的示例中,${user.tasks[2].name}
在元数据中不存在,因此它不应解析为未定义。如果未在元对象中找到密钥,则应将其保留不变,即${user.tasks[2].name}
答案 0 :(得分:2)
仅使用 ES6 方案:
const data = { "name": "Hello ${user.name} ${user.foo}", "primary_task": "Task Name: ${user.tasks[0].name} ${user.tasks[10].name}", "secondary_tasks": ["Task 2: ${user.tasks[0].name}", "Task 2: ${user.tasks[1].name}", "Task 3: ${user.tasks[11].name}"] }
const meta = { "user": { "name": "DJ", "tasks": [ { "name": "Task One" }, { "name": "Task Two" } ] } }
const getPath = (path, obj) => path.split('.').reduce((r, c) =>
r ? c.includes('[') ? getPath(c.replace('[', '.').replace(']', ''), r) : r[c] : undefined, obj)
const interpolate = (s, v) =>
new Function(...Object.keys(v), `return \`${s}\`;`)(...Object.values(v))
const templ = (str, obj) => {
let r = new RegExp(/\${([\s\S]+?)}/g)
while (match = r.exec(str)) {
if (!getPath(match[1], obj))
str = str.replace(match[0], match[0].replace('${', '__'))
}
return interpolate(str, obj).replace('__', '${')
}
const resolve = (d, vars) => {
if (Array.isArray(d))
return d.map(x => templ(x, vars))
else
return Object.entries(d).reduce((r, [k, v]) =>
(r[k] = Array.isArray(v) ? resolve(v, vars) : templ(v, vars), r), {})
}
console.log(resolve(data, meta))
字符串插值的思想受到this thread的影响。这个主意
是递归地遍历所有对象值,并使用interpolate
函数返回实际的水合字符串。遍历路径getPath
以及发现不存在的路径。如果字符串中的路径不存在,则使用字符串替换来使该字符串通过字符串水合,然后我们将其替换。
Lodash _.template 方案:
在可以利用lodash
及其templating mechanism(通过_.template
)的情况下,由于我们已经具有插值功能,因此这变得更简单了,需要解决的问题:
const data = { "name": "Hello ${user.name} ${user.foo}", "primary_task": "Task Name: ${user.tasks[0].name} ${user.tasks[10].name}", "secondary_tasks": ["Task 2: ${user.tasks[0].name}", "Task 2: ${user.tasks[1].name}", "Task 3: ${user.tasks[11].name}"] }
const meta = { "user": { "name": "DJ", "tasks": [ { "name": "Task One" }, { "name": "Task Two" } ] } }
const templ = (str, obj) => {
let r = new RegExp(/\${([\s\S]+?)}/g)
while (match = r.exec(str)) {
if (!_.get(obj, match[1]))
str = str.replace(match[0], match[0].replace('${', '__'))
}
return _.template(str)(obj).replace('__', '${')
}
const resolve = (d, vars) => {
if (_.isArray(d))
return _.map(d, x => templ(x, vars))
else
return _.entries(d).reduce((r, [k, v]) =>
(r[k] = _.isArray(v) ? resolve(v, vars) : templ(v, vars), r), {})
}
console.log(resolve(data, meta))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
这个想法是递归地遍历对象树,并通过lodash _.template
将任何简单的属性转换为字符串。使用Array.map
或_.map
浏览数组,并使用Array.reduce
或_.reduce
浏览对象,以将它们转换为模板字符串。
唯一的问题是需要离开不存在的paths
。为了使该功能正常工作,我们检查不存在哪个路径,将其${
替换为__
,并在对字符串进行补水的情况下完成_.template
函数后,将其替换回去。
答案 1 :(得分:1)
使用loadash实用程序库的方法:
var _ = require('lodash'); // use for node
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/0.10.0/lodash.min.js"></script>; // use for browser
var data = {
"name": "Hello ${user.name}",
"primary_task": "Task Name: Task One",
"secondary_tasks": ["Task 2: ${user.tasks[0].name}", "Task 2: ${user.tasks[1].name}"]
}
var metadata = {
"user": {
"name": "DJ",
"tasks": [{
"name": "Task One"
},
{
"name": "Task Two"
}
],
},
}
var text = JSON.stringify(data); // stringify data object
var myregexp = /\${([\[\]a-z\d.]+)}/i // regex to match the content to be replaced in data
while (match = myregexp.exec(text)) { // loop all matches
try {
// Example: [0]=${user.name} / [1]=user.name
new_data = text.replace(match[0], _.get(metadata, match[1])); // replace values using _ library
text = new_data;
} catch (err) {
console.log("Requested element doesn't exist", err.message);
}
match = myregexp.exec(text);
}
var new_data = JSON.parse(new_data); // convert new_data to object
console.log(new_data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
注意:
答案 2 :(得分:1)
这里是使用纯JS的解决方案,只需使用JSON.stringify,regex,match,replace,reduce和其他方法即可。
确实引起问题的原因是您的“变量”变量似乎格式不正确。 “任务”应该是“用户”的子级。如果确实是您想要的,我已为您修复了该问题。
这绝对是可以优化的,所以让我知道是否有什么可以简化的。
data = {
"name": "Hello ${user.name}",
"primary_task": "Task Name: ${user.tasks[0].name}",
"secondary_tasks": ["Task 2: ${user.tasks[1].name}", "Task 2: ${user.tasks[2].name}"]
}
variables = {
"user": {
"name": "DJ",
"tasks": [{
"name": "Task Primary"
},
{
"name": "Task One"
},
{
"name": "Task Two"
}
]
}
}
const str = JSON.stringify(data);
const reg = /\$\{([a-z]|\[\d\]|\.)+\}/gi
const res = str.match(reg).reduce((acc, cur) => {
//slice to remove ${ and }
const val = cur.slice(2, -1).split(".").reduce((acc2, cur2) => {
//check to see if it's like for example: tasks[1]
if (cur2.indexOf("[") > -1) {
const s = cur2.split("[");
//Ex: acc2["tasks"][0]
//slice to remove trailing "]"
return acc2[s[0]][s[1].slice(0,-1)];
}
//Ex acc2["user"]
return acc2[cur2];
}, variables);
//val contains the value used to replace the variable string name
return acc.replace(cur, val);
}, str);
console.log(JSON.parse(res))
获取数组中所有str变量名称,然后使用reduce循环遍历
const str = '{"name":"Hello ${user.name}","primary_task":"Task Name: ${user.tasks[0].name}","secondary_tasks":["Task 2: ${user.tasks[1].name}","Task 2: ${user.tasks[2].name}"]}'
console.log(str.match(/\$\{([a-z]|\[\d\]|\.)+\}/gi))
对于str.match()数组中的每个值,找到对应的值
const arrayOfStringProperties = "${user.tasks[2].name}".slice(2,-1).split(".")
console.log(arrayOfStringProperties);
//tasks[2] is the one a bit more complicated to handle
const task = "tasks[2]".split("[");
console.log(task[0], task[1].slice(0,-1));
//this allows us to get the value: metadata["user"]["tasks"]["2"]
variables = {"user": {"name": "DJ","tasks": [{"name": "Task Primary"},{"name": "Task One"},{"name": "Task Two"}]}}
console.log(variables["user"]["tasks"]["2"]["name"]);
这是包装在异步函数中的解决方案,可以最终捕获任何错误。
dataNoError = {
"name": "Hello ${user.name}",
"primary_task": "Task Name: ${user.tasks[0].name}",
"secondary_tasks": ["Task 2: ${user.tasks[1].name}", "Task 2: ${user.tasks[2].name}"]
}
dataWithError = {
"name": "Hello ${user.name}",
"primary_task": "Task Name: ${usera.tasks[0].name}",
"secondary_tasks": ["Task 2: ${user.tasks[1].name}", "Task 2: ${user.tasks[2].name}"]
}
variables = {
"user": {
"name": "DJ",
"tasks": [{
"name": "Task Primary"
},
{
"name": "Task One"
},
{
"name": "Task Two"
}
]
}
}
async function substitution(metadata, data) {
const str = JSON.stringify(data);
const reg = /\$\{([a-z]|\[\d\]|\.)+\}/gi
const res = str.match(reg).reduce((acc, cur) => {
//slice to remove ${ and }
const val = cur.slice(2, -1).split(".").reduce((acc2, cur2) => {
//check to see if it's like for example: tasks[1]
if (cur2.indexOf("[") > -1) {
const s = cur2.split("[");
//Ex: acc2["tasks"][0]
//slice to remove trailing "]"
return acc2[s[0]][s[1].slice(0, -1)];
}
//Ex acc2["user"]
return acc2[cur2];
}, metadata);
//val contains the value used to replace the variable string name
return acc.replace(cur, val);
}, str);
return JSON.parse(res);
}
substitution(variables, dataNoError).then(res => console.log(res)).catch(err => console.warn(err.message));
substitution(variables, dataWithError).then(res => console.log(res)).catch(err => console.warn(err.message));
答案 3 :(得分:0)
您可以尝试一下。从本质上讲,它是一个函数(获取),它为您提供路径后的值(如果有),并且一个给定值的函数(replaceWhereDefined)将其替换为您的数据。希望这会有所帮助。
const data = {"name": "Hello ${user.name}", "primary_task": "Task Name: Task One", "secondary_tasks": ["Task 2: ${user.tasks[0].name}", "Task 2: ${user.tasks[2].name}"]};
const meta = {"user": {"name": "DJ", "tasks": [{"name": "Task One"}] } };
const get = (s, meta) => {
const parts = s.replace(/(\${)|}/g, '').split('.');
const value = parts.reduce((acc, val) => {
const isArray = val.match(/\[\d+]/g);
if (isArray) {
const arr = val.match(/[a-zA-Z]+/g).toString();
const position = isArray.toString().replace(/[\[\]]/g, '');
acc = acc[arr];
if (acc) acc = acc[position];
}
else acc = acc[val];
return acc || {};
}, meta);
return typeof value === 'string' ? value : null;
};
const replaceWhereDefined = (data, meta) =>
Object.keys(data).reduce((acc, key) => {
const toReplace = data[key].toString().match(/\${.*?}/g);
if (toReplace) {
toReplace.forEach((path) => {
const value = get(path, meta);
if (Array.isArray(acc[key]) && value) {
acc[key] = acc[key].map((d) => d.replace(path, value));
}
else if (value) acc[key] = acc[key].replace(path, value);
});
}
return acc;
}, data);
console.log(replaceWhereDefined(data, meta));