使用string键访问嵌套的JavaScript对象
我有这样的数据结构:
var someObject = { 'part1' : { 'name': 'Part 1', 'size': '20', 'qty' : '50' }, 'part2' : { 'name': 'Part 2', 'size': '15', 'qty' : '60' }, 'part3' : [ { 'name': 'Part 3A', 'size': '10', 'qty' : '20' }, { 'name': 'Part 3B', 'size': '5', 'qty' : '20' }, { 'name': 'Part 3C', 'size': '7.5', 'qty' : '20' } ] };
我想使用这些variables来访问数据:
var part1name = "part1.name"; var part2quantity = "part2.qty"; var part3name1 = "part3[0].name";
part1name应该填入someObject.part1.name
的值,即“Part 1”。 part2quantity与60填充同样的东西。
有无论如何用纯JavaScript或JQuery来实现这一点?
我只是基于我已经有的一些类似的代码做了这个,它似乎工作:
Object.byString = function(o, s) { s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties s = s.replace(/^\./, ''); // strip a leading dot var a = s.split('.'); for (var i = 0, n = a.length; i < n; ++i) { var k = a[i]; if (k in o) { o = o[k]; } else { return; } } return o; }
用法::
Object.byString(someObj, 'part3[0].name');
在http://jsfiddle.net/alnitak/hEsys/上查看工作演示;
编辑一些已经注意到,这个代码将抛出一个错误,如果传递一个string,其中最左边的索引不对应于对象内正确的嵌套条目。 这是一个有效的关注,但恕我直言调用try / catch
块时最好的地址,而不是这个函数默默地返回undefined
为一个无效的索引。
这是我使用的解决scheme:
Object.resolve = function(path, obj) { return path.split('.').reduce(function(prev, curr) { return prev ? prev[curr] : undefined }, obj || self) }
用法示例:
Object.resolve("document.body.style.width") // or Object.resolve("style.width", document.body) // or even use array indexes // (someObject has been defined in the question) Object.resolve("part3.0.size", someObject) // returns undefined when intermediate properties are not defined: Object.resolve('properties.that.do.not.exist', {hello:'world'})
限制:
- 不能使用方括号(
[]
)作为数组索引(尽pipe指定数组索引的时间间隔工作正常,如上所示) - 名称中包含句点的属性无法访问:
{'my.favorite.number':42}
现在使用_.get(obj, property)
支持lodash。 请参阅https://lodash.com/docs#get
来自文档的示例:
var object = { 'a': [{ 'b': { 'c': 3 } }] }; _.get(object, 'a[0].b.c'); // → 3 _.get(object, ['a', '0', 'b', 'c']); // → 3 _.get(object, 'abc', 'default'); // → 'default'
你必须自己parsingstring:
function getProperty(obj, prop) { var parts = prop.split('.'); if (Array.isArray(parts)) { var last = parts.pop(), l = parts.length, i = 1, current = parts[0]; while((obj = obj[current]) && i < l) { current = parts[i]; i++; } if(obj) { return obj[last]; } } else { throw 'parts is not valid array'; } }
这就要求你也用点符号定义数组索引:
var part3name1 = "part3.0.name";
它使parsing更容易。
DEMO
也适用于对象内部的数组/数组。 防御无效值。
/** * Retrieve nested item from object/array * @param {Object|Array} obj * @param {String} path dot separated * @param {*} def default value ( if result undefined ) * @returns {*} */ function path(obj, path, def){ var i, len; for(i = 0,path = path.split('.'), len = path.length; i < len; i++){ if(!obj || typeof obj !== 'object') return def; obj = obj[path[i]]; } if(obj === undefined) return def; return obj; } ////////////////////////// // TEST // ////////////////////////// var arr = [true, {'sp ace': true}, true] var obj = { 'sp ace': true, arr: arr, nested: {'dotted.str.ing': true}, arr3: arr } shouldThrow(`path(obj, "arr.0")`); shouldBeDefined(`path(obj, "arr[0]")`); shouldBeEqualToNumber(`path(obj, "arr.length")`, 3); shouldBeTrue(`path(obj, "sp ace")`); shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback"); shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
使用eval:
var part1name = eval("someObject.part1.name");
换行返回未定义的错误
function path(obj, path) { try { return eval("obj." + path); } catch(e) { return undefined; } }
http://jsfiddle.net/shanimal/b3xTw/
在使用eval的力量时请使用常识和谨慎。 这有点像一把轻剑,如果打开它,有90%的几率会割断肢体。 它不适合每个人。
ES6 :Vanila JS中只有一行(如果没有find而不是给出错误,则返回null):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
或者例如:
'abc'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
对于准备使用的函数,也可以识别false,0和负数,并接受默认值作为参数:
const resolvePath = (object, path, defaultValue) => path .split('.') .reduce((o, p) => o ? o[p] : defaultValue, object)
例如使用:
resolvePath(window,'document.body') => <body> resolvePath(window,'document.body.xyz') => undefined resolvePath(window,'document.body.xyz', null) => null resolvePath(window,'document.body.xyz', 1) => 1
奖励 :
要设置path(请求由@ rob-gordon),您可以使用:
const setPath = (object, path, value) => path .split('.') .reduce((o,p) => o[p] = path.split('.').pop() === p ? value : o[p] || {}, object)
例:
let myVar = {} setPath(myVar, 'abc', 42) => 42 console.log(myVar) => {a: {b: {c: 42}}}
用[]访问数组 :
const resolvePath = (object, path, defaultValue) => path .split(/[\.\[\]\'\"]/) .filter(p => p) .reduce((o, p) => o ? o[p] : defaultValue, object)
为例
const myVar = {a:{b:[{c:1}]}} resolvePath(myVar,'ab[0].c') => 1 resolvePath(myVar,'a["b"][\'0\'].c') => 1
在这里,我提供了更多的方式,在许多方面看起来更快:
选项1:打开string。 或[或]或者“或”,倒转它,跳过空项目。
function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== 'string') path = '' + path; var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array) while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; } return origin; }
选项2(除eval
以外最快):低级字符扫描(无正则expression式/分割/等,只是一个快速字符扫描)。 注意:这个不支持索引引号。
function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== 'string') path = '' + path; var c = '', pc, i = 0, n = path.length, name = ''; if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c; if (i==n+2) throw "Invalid path: "+path; return origin; } // (around 1,000,000+/- ops/sec)
选项3 🙁 新增 :选项2扩展为支持报价 – 稍慢,但仍然快)
function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== 'string') path = '' + path; var c, pc, i = 0, n = path.length, name = '', q; while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c; if (i==n+2 || name) throw "Invalid path: "+path; return origin; }
JSPerf: http ://jsperf.com/ways-to-dereference-a-delimited-property-string/3
“eval(…)”仍然是国王(性能明智的是)。 如果您的财产path直接在您的控制之下,那么使用“eval”(特别是在需要速度的情况下)应该没有任何问题。 如果拉动属性path“在线上”( 在线 !?lol:P),那么是的,使用其他的东西是安全的。 只有一个白痴会说永远不会使用“eval”,因为使用它时有很好的理由 。 此外,“它在Doug Crockford的JSONparsing器中使用 。 如果input是安全的,那么根本没有问题。 正确的工作使用正确的工具,就是这样。
您可以使用点符号来获取深层对象成员的值,而无需使用任何外部JavaScript库,只需使用以下简单技巧:
new Function('_', 'return _.' + path)(obj);
在你的情况下,从someObject
获取part1.name
值只是做:
new Function('_', 'return _.part1.name')(someObject);
这里是一个简单的小提琴演示: https : //jsfiddle.net/harishanchu/oq5esowf/
我想你是这样问的:
var part1name = someObject.part1.name; var part2quantity = someObject.part2.qty; var part3name1 = someObject.part3[0].name;
你可能会问这个:
var part1name = someObject["part1"]["name"]; var part2quantity = someObject["part2"]["qty"]; var part3name1 = someObject["part3"][0]["name"];
这两者都可以工作
或者,也许你正在问这个
var partName = "part1"; var nameStr = "name"; var part1name = someObject[partName][nameStr];
最后你可能会问这个
var partName = "part1.name"; var partBits = partName.split("."); var part1name = someObject[partBits[0]][partBits[1]];
Speigg的方法是非常整洁,虽然我发现这个答复,同时寻找通过stringpath访问AngularJS $范围属性的解决scheme,并做了一些修改它的工作:
$scope.resolve = function( path, obj ) { return path.split('.').reduce( function( prev, curr ) { return prev[curr]; }, obj || this ); }
只要将这个函数放在你的根控制器中,并像下面这样使用它的任何子范围:
$scope.resolve( 'path.to.any.object.in.scope')
这里是所有4的性能testing,@TheZver和@Shanimal是赢家:
Part 1 60 Part 3A Object.byString: 2.536ms Part 1 60 Part 3A getProperty: 0.274ms Part 1 60 undefined eval: 0.657ms Part 1 60 Part 3A path: 0.256ms
我还没有find一个包做所有的操作与一个stringpath,所以我写了我自己的快速小包,它支持插入(),得到()(与默认返回),设置()和删除)操作。
您可以使用点符号,括号,数字索引,string编号属性和非单词字符的键。 简单的用法如下:
> var jsocrud = require('jsocrud'); ... // Get (Read) --- > var obj = { > foo: [ > { > 'key w/ non-word chars': 'bar' > } > ] > }; undefined > jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]'); 'bar'
现在有一个npm
模块用于执行此操作: https : //github.com/erictrinh/safe-access
用法示例:
var access = require('safe-access'); access(very, 'nested.property.and.array[0]');
/** * Access a deep value inside a object * Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz" * @author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e * Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/ */ function getDeepVal(obj, path) { if (typeof obj === "undefined" || obj === null) return; path = path.split(/[\.\[\]\"\']{1,2}/); for (var i = 0, l = path.length; i < l; i++) { if (path[i] === "") continue; obj = obj[path[i]]; if (typeof obj === "undefined" || obj === null) return; } return obj; }
与…合作
getDeepVal(obj,'foo.bar') getDeepVal(obj,'foo.1.bar') getDeepVal(obj,'foo[0].baz') getDeepVal(obj,'foo[1][2]') getDeepVal(obj,"foo['bar'].baz") getDeepVal(obj,"foo['bar']['baz']") getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
如果您需要在编码时间不知道它的情况下访问不同的嵌套键(解决这些问题将会很简单),那么可以使用数组标记访问器:
var part1name = someObject['part1']['name']; var part2quantity = someObject['part2']['qty']; var part3name1 = someObject['part3'][0]['name'];
它们相当于点符号存取器,可能在运行时会有所不同,例如:
var part = 'part1'; var property = 'name'; var part1name = someObject[part][property];
相当于
var part1name = someObject['part1']['name'];
要么
var part1name = someObject.part1.name;
我希望这个解决您的问题…
编辑
我不会使用一个string来获得一种xpath 查询来访问一个对象值。 由于你必须调用一个函数来parsing查询并检索值,我会按照另一个path(不是:
var part1name = function(){ return this.part1.name; } var part2quantity = function() { return this['part2']['qty']; } var part3name1 = function() { return this.part3[0]['name'];} // usage: part1name.apply(someObject);
或者,如果你对申请方法感到不安
var part1name = function(obj){ return obj.part1.name; } var part2quantity = function(obj) { return obj['part2']['qty']; } var part3name1 = function(obj) { return obj.part3[0]['name'];} // usage: part1name(someObject);
function更短,更清晰,解释器检查你的语法错误等等。
顺便说一句,我觉得在适当的时候做一个简单的任务就足够了。
刚刚有同样的问题,并成功地使用https://npmjs.org/package/tea-properties其中也;set
嵌套对象/数组:
得到:
var o = { prop: { arr: [ {foo: 'bar'} ] } }; var properties = require('tea-properties'); var value = properties.get(o, 'prop.arr[0].foo'); assert(value, 'bar'); // true
组:
var o = {}; var properties = require('tea-properties'); properties.set(o, 'prop.arr[0].foo', 'bar'); assert(o.prop.arr[0].foo, 'bar'); // true
那么这个解决scheme呢?
setJsonValue: function (json, field, val) { if (field !== undefined){ try { eval("json." + field + " = val"); } catch(e){ ; } } }
而这个,为了得到:
getJsonValue: function (json, field){ var value = undefined; if (field !== undefined) { try { eval("value = json." + field); } catch(e){ ; } } return value; };
可能有些人会认为他们是不安全的,但是他们必须更快地parsingstring。
下划线有一个可用的函数叫做getNested(obj, chain, def, opts)
在这里可以看到…
https://github.com/dsc/underscore.nested/blob/master/underscore.nested.js#L182
用咖啡标记你可以使用? 符号来检查对象是否存在,如果是这样的话,按照这样的链:
part1name = someObject.part1?.name; part2quantity = someObject.part2?.qty; part3name1 = part3?[0]?.name;
如果链上的某些属性不存在,则值将不确定:-)
这里的解决scheme只是为了访问深度嵌套的键。 我需要一个访问,添加,修改和删除密钥。 这就是我想到的:
var deepAccessObject = function(object, path_to_key, type_of_function, value){ switch(type_of_function){ //Add key/modify key case 0: if(path_to_key.length === 1){ if(value) object[path_to_key[0]] = value; return object[path_to_key[0]]; }else{ if(object[path_to_key[0]]) return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value); else object[path_to_key[0]] = {}; } break; //delete key case 1: if(path_to_key.length === 1){ delete object[path_to_key[0]]; return true; }else{ if(object[path_to_key[0]]) return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value); else return false; } break; default: console.log("Wrong type of function"); } };
-
path_to_key
:数组中的path。 你可以用你的string_path.split(".")
replace它。 -
type_of_function
:0用于访问(不传递任何值的value
),0用于添加和修改。 1删除。
这是一个lodash单线程。
const deep = { l1: { l2: { l3: "Hello" } } }; const prop = "l1.l2.l3"; const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep); // val === "Hello"
Plunkr
简单的function,允许一个string或数组path。
function get(obj, path) { if(typeof path === 'string') path = path.split('.'); if(path.length === 0) return obj; return get(obj[path[0]], path.slice(1)); } const obj = {a: {b: {c: 'foo'}}}; console.log(get(obj, 'abc')); //foo
要么
console.log(get(obj, ['a', 'b', 'c'])); //foo
build立在Alnitak的答案:
if(!Object.prototype.byString){ //NEW byString which can update values Object.prototype.byString = function(s, v, o) { var _o = o || this; s = s.replace(/\[(\w+)\]/g, '.$1'); // CONVERT INDEXES TO PROPERTIES s = s.replace(/^\./, ''); // STRIP A LEADING DOT var a = s.split('.'); //ARRAY OF STRINGS SPLIT BY '.' for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS var k = a[i]; if (k in _o) {//LOOP THROUGH OBJECT KEYS if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM if(i === a.length -1){//IF IT'S THE LAST IN THE ARRAY _o[k] = v; } } _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE } } else { return; } } return _o; };
}
这允许你设置一个值!
我也创build了一个npm包和github
使用streamapi:
var your_object = { foo: { bar : [1, "hello", 5] } } function deep_access(deep_object, path, splitter) { return path.split(splitter).reduce(function (acc, val) { return acc[val]; }, deep_object); } console.log(deep_access(your_object, "foo.bar.1", "."));
例如: ["my_field", "another_field", 0, "last_field", 10]
可以使用数组来替代嵌套的对象和数组。
这里是一个基于这个数组表示来改变字段的例子。 我在react.js中使用类似于控制input字段的东西来改变嵌套结构的状态。
let state = { test: "test_value", nested: { level1: "level1 value" }, arr: [1, 2, 3], nested_arr: { arr: ["buh", "bah", "foo"] } } function handleChange(value, fields) { let update_field = state; for(var i = 0; i < fields.length - 1; i++){ update_field = update_field[fields[i]]; } update_field[fields[fields.length-1]] = value; } handleChange("update", ["test"]); handleChange("update_nested", ["nested","level1"]); handleChange(100, ["arr",0]); handleChange('changed_foo', ["nested_arr", "arr", 3]); console.log(state);
根据以前的答案,我创build了一个可以处理括号的函数。 但由于分裂,他们内部没有点。
function get(obj, str) { return str.split(/\.|\[/g).map(function(crumb) { return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1")); }).reduce(function(obj, prop) { return obj ? obj[prop] : undefined; }, obj); }
我敢肯定我会为此而投票赞成,但这里是最短的版本:
const resolve = (obj, path) => { try { return eval(`${obj}.${path}`); } catch (err) { return void 0; } };