JavaScripttesting是否存在嵌套的对象键
如果我有一个对象的引用 –
var test = {};
这可能(但不是立即)嵌套对象,就像 –
{ level1:{level2:{level3:'level3'}} };
testing最深层嵌套对象中键的存在的最好方法是什么?
这个 –
alert(test.level1);
返回“未定义”,但是这个 –
alert(test.level1.level2.level3);
失败。
我目前正在做这样的事情 –
if(test.level1 && test.level1.level2 && test.level1.level2.level3) { alert(test.level1.level2.level3); }
但我想知道是否有更好的方法。
如果您不想要TypeError
,则必须一步一步完成,因为如果其中一个成员为null
或undefined
,并且您尝试访问成员,则将引发exception。
你可以简单地catch
exception,或者创build一个函数来testing多个层次的存在,如下所示:
function checkNested(obj /*, level1, level2, ... levelN*/) { var args = Array.prototype.slice.call(arguments, 1); for (var i = 0; i < args.length; i++) { if (!obj || !obj.hasOwnProperty(args[i])) { return false; } obj = obj[args[i]]; } return true; } var test = {level1:{level2:{level3:'level3'}} }; checkNested(test, 'level1', 'level2', 'level3'); // true checkNested(test, 'level1', 'level2', 'foo'); // false
这是我从Oliver Steele领取的一种模式:
var level3 = (((test || {}).level1 || {}).level2 || {}).level3; alert( level3 );
事实上,整篇文章是关于如何在JavaScript中做到这一点的讨论。 他决定使用上面的语法(一旦习惯了,就不难读懂了)作为一个习语。
更新
看起来像lodash 已经添加 _.get
所有你的嵌套属性获得需要。
_.get(countries, 'greece.sparta.playwright')
以前的答案
lodash用户可能会喜欢lodash.contrib有一些方法来缓解这个问题 。
的getPath
签名: _.getPath(obj:Object, ks:String|Array)
根据给定键所描述的path,获取嵌套对象中任何深度的值。 键可以以数组或点分隔的string的forms给出。 如果无法到达path,则返回undefined
。
var countries = { greece: { athens: { playwright: "Sophocles" } } } }; _.getPath(countries, "greece.athens.playwright"); // => "Sophocles" _.getPath(countries, "greece.sparta.playwright"); // => undefined _.getPath(countries, ["greece", "athens", "playwright"]); // => "Sophocles" _.getPath(countries, ["greece", "sparta", "playwright"]); // => undefined
我已经做了性能testing (谢谢你cdMinix添加lodash)对这个问题提出的一些build议与下面列出的结果。
免责声明#1将string转换为引用是不必要的元编程,可能是最好的避免。 不要忘记你的参考开始。 从这个答案阅读更多类似的问题 。
免责声明#2我们正在这里讨论每毫秒数百万次操作。 在大多数使用情况下,这些都不会有太大的区别。 select哪一个最明智地知道每个的限制。 对我来说,我会去
reduce
方便的东西。
对象包装(由奥利弗斯蒂尔) – 34% – 最快
var r1 = (((test || {}).level1 || {}).level2 || {}).level3; var r2 = (((test || {}).level1 || {}).level2 || {}).foo;
原始解决scheme(build议问题) – 45%
var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3; var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;
checkNested – 50%
function checkNested(obj) { for (var i = 1; i < arguments.length; i++) { if (!obj.hasOwnProperty(arguments[i])) { return false; } obj = obj[arguments[i]]; } return true; }
get_if_exist – 52%
function get_if_exist(str) { try { return eval(str) } catch(e) { return undefined } }
有效链 – 54%
function validChain( object, ...keys ) { return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined; }
objHasKeys – 63%
function objHasKeys(obj, keys) { var next = keys.shift(); return obj[next] && (! keys.length || objHasKeys(obj[next], keys)); }
nestedPropertyExists – 69%
function nestedPropertyExists(obj, props) { var prop = props.shift(); return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false; }
_.get – 72%
最深 – 86%
function deeptest(target, s){ s= s.split('.') var obj= target[s.shift()]; while(obj && s.length) obj= obj[s.shift()]; return obj; }
伤心的小丑 – 100% – 最慢
var o = function(obj) { return obj || {} }; var r1 = o(o(o(o(test).level1).level2).level3); var r2 = o(o(o(o(test).level1).level2).foo);
如果您像string一样处理名称,则可以在任意深度读取对象属性: 't.level1.level2.level3'
。
window.t={level1:{level2:{level3: 'level3'}}}; function deeptest(s){ s= s.split('.') var obj= window[s.shift()]; while(obj && s.length) obj= obj[s.shift()]; return obj; } alert(deeptest('t.level1.level2.level3') || 'Undefined');
如果任何一个段undefined
它将返回undefined
。
var a; a = { b: { c: 'd' } }; function isset (fn) { var value; try { value = fn(); } catch (e) { value = undefined; } finally { return value !== undefined; } }; // ES5 console.log( isset(function () { return abc; }), isset(function () { return abcdef; }) );
如果您在ES6环境中编码(或使用6to5 ),则可以利用箭头函数语法:
// ES6 using the arrow function console.log( isset(() => abc), isset(() => abcdef) );
关于性能,如果设置了属性,则不会有使用try..catch
块的性能损失。 如果该属性未设置,则会对性能产生影响。
考虑简单地使用_.has
:
var object = { 'a': { 'b': { 'c': 3 } } }; _.has(object, 'a'); // → true _.has(object, 'abc'); // → true _.has(object, ['a', 'b', 'c']); // → true
怎么样
try { alert(test.level1.level2.level3) } catch(e) { ...whatever }
我尝试了一个recursion的方法:
function objHasKeys(obj, keys) { var next = keys.shift(); return obj[next] && (! keys.length || objHasKeys(obj[next], keys)); }
那! keys.length ||
! keys.length ||
踢出recursion,所以它不运行没有任何密钥的function来testing。 testing:
obj = { path: { to: { the: { goodKey: "hello" } } } } console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey'])); // undefined
我正在使用它来打印一个友好的HTML视图的一堆对象与未知的键/值,例如:
var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':')) ? myObj.MachineInfo.BiosInfo.Name : 'unknown';
一个简单的方法是:
try { alert(test.level1.level2.level3); } catch(e) { alert("undefined"); // this is optional to put any output here }
try/catch
捕获任何更高级别的对象,例如test,test.level1,test.level1.level2没有定义的情况。
@ CMS较短的ES5版本的优秀答案:
// Check the obj has the keys in the order mentioned. Used for checking JSON results. var checkObjHasKeys = function(obj, keys) { var success = true; keys.forEach( function(key) { if ( ! obj.hasOwnProperty(key)) { success = false; } obj = obj[key]; }) return success; }
有了类似的testing:
var test = { level1:{level2:{level3:'result'}}}; utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false
基于这个答案 ,我想出了使用ES2015
这个通用函数来解决问题
function validChain( object, ...keys ) { return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined; } var test = { first: { second: { third: "This is not the key your are looking for" } } } if ( validChain( test, "first", "second", "third" ) ) { console.log( test.first.second.third ); }
我认为下面的脚本提供了更多可读的表示。
声明一个函数:
var o = function(obj) { return obj || {};};
然后像这样使用它:
if (o(o(o(o(test).level1).level2).level3) { }
我把它称为“悲伤的小丑技术”,因为它使用符号o(
编辑:
这里是TypeScript的一个版本
它会在编译时进行types检查(以及如果使用像Visual Studio这样的工具,则可以使用intellisense)
export function o<T>(someObject: T, defaultValue: T = {} as T) : T { if (typeof someObject === 'undefined' || someObject === null) return defaultValue; else return someObject; }
用法是一样的:
o(o(o(o(test).level1).level2).level3
但是这次intellisense的作品!
另外,你可以设置一个默认值:
o(o(o(o(o(test).level1).level2).level3, "none")
我没有看到有人使用代理的例子
所以我想出了我自己的。 格栅的事情是,你不必插入string。 你实际上可以返回一个链式对象函数,并用它做一些神奇的事情。 您甚至可以调用函数并获取数组索引来检查深层对象
function resolve(target) { var noop = () => {} // We us a noop function so we can call methods also return new Proxy(noop, { get(noop, key) { // return end result if key is _result return key === '_result' ? target : resolve( // resolve with target value or undefined target === undefined ? undefined : target[key] ) }, // if we want to test a function then we can do so alos thanks to using noop // instead of using target in our proxy apply(noop, that, args) { return resolve(typeof target === 'function' ? target.apply(that, args) : undefined) }, }) } // some modified examples from the accepted answer var test = {level1: {level2:() => ({level3:'level3'})}} var test1 = {key1: {key2: ['item0']}} // You need to get _result in the end to get the final result console.log(resolve(test).level1.level2().level3._result) console.log(resolve(test).level1.level2().level3.level4.level5._result) console.log(resolve(test1).key1.key2[0]._result) console.log(resolve(test1)[0].key._result) // don't exist
由CMS给出的答案也适用于以下的空检查修改
function checkNested(obj /*, level1, level2, ... levelN*/) { var args = Array.prototype.slice.call(arguments), obj = args.shift(); for (var i = 0; i < args.length; i++) { if (obj == null || !obj.hasOwnProperty(args[i]) ) { return false; } obj = obj[args[i]]; } return true; }
我知道这个问题是旧的,但我想通过将其添加到所有对象来提供一个扩展。 我知道人们倾向于使用对象原型来扩展对象function,但是我不觉得比做这个更容易。 另外,现在允许使用Object.defineProperty方法。
Object.defineProperty( Object.prototype, "has", { value: function( needle ) { var obj = this; var needles = needle.split( "." ); for( var i = 0; i<needles.length; i++ ) { if( !obj.hasOwnProperty(needles[i])) { return false; } obj = obj[needles[i]]; } return true; }});
现在,为了testing任何物体的任何属性,你可以简单地做:
if( obj.has("some.deep.nested.object.somewhere") )
这里有一个jsfiddle来testing它,特别是它包含了一些jQuery,如果你直接修改Object.prototype是因为它的属性变为可枚举的,就会中断它。 这应该适用于第三方库。
这适用于所有对象和数组:)
例如:
if( obj._has( "something.['deep']['under'][1][0].item" ) ) { //do something }
这是我的改进版本的布赖恩的答案
我用_has作为属性名称,因为它可能与现有的属性(如:地图)
Object.defineProperty( Object.prototype, "_has", { value: function( needle ) { var obj = this; var needles = needle.split( "." ); var needles_full=[]; var needles_square; for( var i = 0; i<needles.length; i++ ) { needles_square = needles[i].split( "[" ); if(needles_square.length>1){ for( var j = 0; j<needles_square.length; j++ ) { if(needles_square[j].length){ needles_full.push(needles_square[j]); } } }else{ needles_full.push(needles[i]); } } for( var i = 0; i<needles_full.length; i++ ) { var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/); if (res != null) { for (var j = 0; j < res.length; j++) { if (res[j] != undefined) { needles_full[i] = res[j]; } } } if( typeof obj[needles_full[i]]=='undefined') { return false; } obj = obj[needles_full[i]]; } return true; }});
这是小提琴
这里是我的看法 – 大多数解决scheme忽略了嵌套数组的情况,如下所示:
obj = { "l1":"something", "l2":[{k:0},{k:1}], "l3":{ "subL":"hello" } }
我可能想检查obj.l2[0].k
使用下面的函数,你可以做deeptest('l2[0].k',obj)
如果对象存在,函数将返回true,否则返回false
function deeptest(keyPath, testObj) { var obj; keyPath = keyPath.split('.') var cKey = keyPath.shift(); function get(pObj, pKey) { var bracketStart, bracketEnd, o; bracketStart = pKey.indexOf("["); if (bracketStart > -1) { //check for nested arrays bracketEnd = pKey.indexOf("]"); var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1); pKey = pKey.substr(0, bracketStart); var n = pObj[pKey]; o = n? n[arrIndex] : undefined; } else { o = pObj[pKey]; } return o; } obj = get(testObj, cKey); while (obj && keyPath.length) { obj = get(obj, keyPath.shift()); } return typeof(obj) !== 'undefined'; } var obj = { "l1":"level1", "arr1":[ {"k":0}, {"k":1}, {"k":2} ], "sub": { "a":"letter A", "b":"letter B" } }; console.log("l1: " + deeptest("l1",obj)); console.log("arr1[0]: " + deeptest("arr1[0]",obj)); console.log("arr1[1].k: " + deeptest("arr1[1].k",obj)); console.log("arr1[1].j: " + deeptest("arr1[1].j",obj)); console.log("arr1[3]: " + deeptest("arr1[3]",obj)); console.log("arr2: " + deeptest("arr2",obj));
现在我们也可以使用reduce
通过嵌套键进行循环:
// @params o<object> // @params path<string> expects 'obj.prop1.prop2.prop3' // returns: obj[path] value or 'false' if prop doesn't exist const objPropIfExists = o => path => { const levels = path.split('.'); const res = (levels.length > 0) ? levels.reduce((a, c) => a[c] || 0, o) : o[path]; return (!!res) ? res : false } const obj = { name: 'Name', sys: { country: 'AU' }, main: { temp: '34', temp_min: '13' }, visibility: '35%' } const exists = objPropIfExists(obj)('main.temp') const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz') console.log(exists, doesntExist)
我想我今天会再添加一个。 我为这个解决scheme感到自豪的原因是它避免了许多解决scheme中使用的嵌套括号,例如Object Wrap(Oliver Steele) :
(在这个例子中我使用下划线作为占位符variables,但任何variables名都可以)
//the 'test' object var test = {level1: {level2: {level3: 'level3'}}}; let _ = test; if ((_=_.level1) && (_=_.level2) && (_=_.level3)) { let level3 = _; //do stuff with level3 }
在这里代码的一个函数(safeRead) ,这将以安全的方式做到这一点…即
safeRead(test, 'level1', 'level2', 'level3');
如果任何属性为空或未定义,则返回空string
根据之前的评论 ,这里是另一个版本,主要的对象不能被定义:
// Supposing that our property is at first.second.third.property: var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;
以下选项是从这个答案开始阐述的。 同样的树:
var o = { a: { b: { c: 1 } } };
停止search时,未定义
var u = undefined; oa ? oab ? oabc : u : u // 1 ox ? oxy ? oxyz : u : u // undefined (o = oa) ? (o = ob) ? oc : u : u // 1
确保每个级别一个一个
var $ = function (empty) { return function (node) { return node || empty; }; }({}); $($(oa).b).c // 1 $($(ox).y).z // undefined
//Just in case is not supported or not included by your framework //*************************************************** Array.prototype.some = function(fn, thisObj) { var scope = thisObj || window; for ( var i=0, j=this.length; i < j; ++i ) { if ( fn.call(scope, this[i], i, this) ) { return true; } } return false; }; //**************************************************** function isSet (object, string) { if (!object) return false; var childs = string.split('.'); if (childs.length > 0 ) { return !childs.some(function (item) { if (item in object) { object = object[item]; return false; } else return true; }); } else if (string in object) { return true; } else return false; } var object = { data: { item: { sub_item: { bla: { here : { iam: true } } } } } }; console.log(isSet(object,'data.item')); // true console.log(isSet(object,'x')); // false console.log(isSet(object,'data.sub_item')); // false console.log(isSet(object,'data.item')); // true console.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true
我认为这是一个小小的改进(成为一个class轮):
alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )
这是有效的,因为&&操作符返回它所评估的最终操作数(并且它是短路的)。
如果这个属性存在,我正在寻找要返回的值,所以我通过上面的CMS修改了答案。 以下是我想到的:
function getNestedProperty(obj, key) { // Get property array from key string var properties = key.split("."); // Iterate through properties, returning undefined if object is null or property doesn't exist for (var i = 0; i < properties.length; i++) { if (!obj || !obj.hasOwnProperty(properties[i])) { return; } obj = obj[properties[i]]; } // Nested property found, so return the value return obj; } Usage: getNestedProperty(test, "level1.level2.level3") // "level3" getNestedProperty(test, "level1.level2.foo") // undefined
我有同样的问题,并想看看我能不能提出一个我自己的解决scheme。 这接受你想要检查的path作为一个string。
function checkPathForTruthy(obj, path) { if (/\[[a-zA-Z_]/.test(path)) { console.log("Cannot resolve variables in property accessors"); return false; } path = path.replace(/\[/g, "."); path = path.replace(/]|'|"/g, ""); path = path.split("."); var steps = 0; var lastRef = obj; var exists = path.every(key => { var currentItem = lastRef[path[steps]]; if (currentItem) { lastRef = currentItem; steps++; return true; } else { return false; } }); return exists; }
这里有一些logging和testing用例的片段:
console.clear(); var testCases = [ ["data.Messages[0].Code", true], ["data.Messages[1].Code", true], ["data.Messages[0]['Code']", true], ['data.Messages[0]["Code"]', true], ["data[Messages][0]['Code']", false], ["data['Messages'][0]['Code']", true] ]; var path = "data.Messages[0].Code"; var obj = { data: { Messages: [{ Code: "0" }, { Code: "1" }] } } function checkPathForTruthy(obj, path) { if (/\[[a-zA-Z_]/.test(path)) { console.log("Cannot resolve variables in property accessors"); return false; } path = path.replace(/\[/g, "."); path = path.replace(/]|'|"/g, ""); path = path.split("."); var steps = 0; var lastRef = obj; var logOutput = []; var exists = path.every(key => { var currentItem = lastRef[path[steps]]; if (currentItem) { logOutput.push(currentItem); lastRef = currentItem; steps++; return true; } else { return false; } }); console.log(exists, logOutput); return exists; } testCases.forEach(testCase => { if (checkPathForTruthy(obj, testCase[0]) === testCase[1]) { console.log("Passed: " + testCase[0]); } else { console.log("Failed: " + testCase[0] + " expected " + testCase[1]); } });
对此答案进行轻微编辑,以允许在path中嵌套数组
var has = function (obj, key) { return key.split(".").every(function (x) { if (typeof obj != "object" || obj === null || !x in obj) return false; if (obj.constructor === Array) obj = obj[0]; obj = obj[x]; return true; }); }
我自动化了这个过程
if(isset(object,["prop1","prop2"])){ // YES! } function isset(object, props){ var dump; try { for(var x in props){ if(x == 0) { dump = object[props[x]]; return; } dump = dump[props[x]]; } } catch(e) { return false; } return true; }
Another ES5 solution:
function hasProperties(object, properties) { return !properties.some(function(property){ if (!object.hasOwnProperty(property)) { return true; } object = object[property]; return false; }); }
My solution that I use since long time (using string unfortunaly, couldn't find better)
function get_if_exist(str){ try{return eval(str)} catch(e){return undefined} } // way to use if(get_if_exist('test.level1.level2.level3')) { alert(test.level1.level2.level3); } // or simply alert(get_if_exist('test.level1.level2.level3'));
edit: this work only if object "test" have global scope/range. else you have to do something like :
// i think it's the most beautiful code I have ever write :p function get_if_exist(obj){ return arguments.length==1 || (obj[arguments[1]] && get_if_exist.apply(this,[obj[arguments[1]]].concat([].slice.call(arguments,2)))); } alert(get_if_exist(test,'level1','level2','level3'));
edit final version to allow 2 method of call :
function get_if_exist(obj){ var a=arguments, b=a.callee; // replace a.callee by the function name you choose because callee is depreceate, in this case : get_if_exist // version 1 calling the version 2 if(a[1] && ~a[1].indexOf('.')) return b.apply(this,[obj].concat(a[1].split('.'))); // version 2 return a.length==1 ? a[0] : (obj[a[1]] && b.apply(this,[obj[a[1]]].concat([].slice.call(a,2)))); } // method 1 get_if_exist(test,'level1.level2.level3'); // method 2 get_if_exist(test,'level1','level2','level3');