在没有 eval 的情况下使用字符串键访问或创建嵌套的 JavaScript 对象
Accessing or creating nested JavaScript objects with string key without eval
我正在寻找一个很好的解决方案来通过字符串值访问 属性,但是如果 属性 不存在,它应该创建它。如果根结构已经定义了结构的某些部分,属性不应该被覆盖,而是合并。
例如,如果您有一个空对象 test
并且您想要在不使用 eval 的情况下设置深层结构。例如
test = {}
test.foo.name = "Hallo" // <<- foo is an Object
test.foo[3] = "Test" // <<- foo should remain as Object, not as Array
test.foo.data[3].bar = 100 // <<- should not overwrite test.foo.name
我写了一个实际可行的解决方案,但它是相当糟糕的代码,我猜:
也可作为 jsfiddle 使用:https://jsfiddle.net/gvaLzqqf/4/
Object.setValue = function(node, flatKey, value) {
flatKey = flatKey.replace("[", ".");
flatKey = flatKey.replace("]", "");
var parts = flatKey.split(".")
var oldNode = node
parts.forEach(function(key, index) {
if (/^\+?(0|[1-9]\d*)$/.test(key)) {
key = key * 1
if (index > 0) {
var oldValue = parts[index - 1]
if (!Array.isArray(oldNode[oldValue])) {
oldNode[oldValue] = []
node = oldNode[oldValue]
}
}
}
if (node[key] == undefined) {
node[key] = {}
}
oldNode = node
node = node[key]
}); // for each
oldNode[parts[parts.length - 1]] = value
return oldNode[parts[parts.length - 1]]
} // function
var test = {}
Object.setValue(test, "foo.name", "Mr. Foo")
Object.setValue(test, "foo.data[0].bar", 100)
Object.setValue(test, "and.another[2].deep", 20)
console.log("test = " + JSON.stringify(test))
console.log("test.foo.data[0].bar = " + test.foo.data[0].bar)
但是,有没有更好的方法来实现这个目标?
我不会在这种情况下重新发明轮子,而是使用 lodash. Specifically the set() 函数。按照他们的例子:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5
您可以通过行走给定的对象来分割路径并减少路径。如果不存在对象,则使用名称或数组创建一个新的 属性。稍后赋值。
function setValue(object, path, value) {
var way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
var test = {};
setValue(test, "foo.name", "Mr. Foo");
setValue(test, "foo.data[0].bar", 100);
setValue(test, "and.another[2].deep", 20);
console.log(test);
我正在寻找一个很好的解决方案来通过字符串值访问 属性,但是如果 属性 不存在,它应该创建它。如果根结构已经定义了结构的某些部分,属性不应该被覆盖,而是合并。
例如,如果您有一个空对象 test
并且您想要在不使用 eval 的情况下设置深层结构。例如
test = {}
test.foo.name = "Hallo" // <<- foo is an Object
test.foo[3] = "Test" // <<- foo should remain as Object, not as Array
test.foo.data[3].bar = 100 // <<- should not overwrite test.foo.name
我写了一个实际可行的解决方案,但它是相当糟糕的代码,我猜:
也可作为 jsfiddle 使用:https://jsfiddle.net/gvaLzqqf/4/
Object.setValue = function(node, flatKey, value) {
flatKey = flatKey.replace("[", ".");
flatKey = flatKey.replace("]", "");
var parts = flatKey.split(".")
var oldNode = node
parts.forEach(function(key, index) {
if (/^\+?(0|[1-9]\d*)$/.test(key)) {
key = key * 1
if (index > 0) {
var oldValue = parts[index - 1]
if (!Array.isArray(oldNode[oldValue])) {
oldNode[oldValue] = []
node = oldNode[oldValue]
}
}
}
if (node[key] == undefined) {
node[key] = {}
}
oldNode = node
node = node[key]
}); // for each
oldNode[parts[parts.length - 1]] = value
return oldNode[parts[parts.length - 1]]
} // function
var test = {}
Object.setValue(test, "foo.name", "Mr. Foo")
Object.setValue(test, "foo.data[0].bar", 100)
Object.setValue(test, "and.another[2].deep", 20)
console.log("test = " + JSON.stringify(test))
console.log("test.foo.data[0].bar = " + test.foo.data[0].bar)
但是,有没有更好的方法来实现这个目标?
我不会在这种情况下重新发明轮子,而是使用 lodash. Specifically the set() 函数。按照他们的例子:
var object = { 'a': [{ 'b': { 'c': 3 } }] };
_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4
_.set(object, ['x', '0', 'y', 'z'], 5);
console.log(object.x[0].y.z);
// => 5
您可以通过行走给定的对象来分割路径并减少路径。如果不存在对象,则使用名称或数组创建一个新的 属性。稍后赋值。
function setValue(object, path, value) {
var way = path.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = way.pop();
way.reduce(function (o, k, i, kk) {
return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
}, object)[last] = value;
}
var test = {};
setValue(test, "foo.name", "Mr. Foo");
setValue(test, "foo.data[0].bar", 100);
setValue(test, "and.another[2].deep", 20);
console.log(test);