测试嵌套JavaScript对象键的存在


691

如果我有一个对象的引用:

var test = {};

可能(但不是立即)具有嵌套对象,例如:

{level1: {level2: {level3: "level3"}}};

检查深度嵌套对象中属性是否存在的最佳方法是什么?

alert(test.level1);产生undefined,但alert(test.level1.level2.level3);失败。

我目前正在做这样的事情:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

但我想知道是否有更好的方法。


1
你可能要检查的是被问最近切向相关的问题stackoverflow.com/questions/2525943/...
阿努拉格


那里有几个命题:stackoverflow.com/a/18381564/1636522
叶子

如果level3属性为false,则您当前的方法可能会出现问题,在这种情况下,即使该属性存在也会返回nfalse,也请看一下此示例jsfiddle.net/maz9bLjx
GibboK 2015年

10
只需您也可以使用try catch
Raghavendra

Answers:


486

如果您不想要a,则必须分步执行,TypeError因为如果其中一个成员是nullundefined,并且您尝试访问成员,则将引发异常。

您可以简单地catch指定异常,也可以创建一个函数来测试是否存在多个级别,如下所示:

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

ES6更新:

这是原始功能的简化版本,使用了ES6功能和递归功能(也采用正确的尾调用形式):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

但是,如果要获取嵌套属性的值,而不仅要检查其存在,那么这里有一个简单的单行函数:

function getNested(obj, ...args) {
  return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'
console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6
console.log(getNested(test, 'level1', 'level2', 'foo')); // undefined
console.log(getNested(test, 'a', 'b')); // undefined

上面的函数允许您获取嵌套属性的值,否则将返回undefined

更新2019-10-17:

可选的链接建议对达到第3阶段ECMAScript委员会的过程,这将让你安全地访问深度嵌套的性质,通过使用令牌?.,新的可选链接操作

const value = obj?.level1?.level2?.level3 

如果所访问的任何级别是nullundefined表达式将undefined自行解析为。

该建议还允许您安全地处理方法调用:

obj?.level1?.method();

上面的表达式将产生undefined如果objobj.level1或者obj.level1.methodnullundefined,否则会调用该函数。

您可以使用可选的链接插件通过Babel开始使用此功能。

Babel 7.8.0开始,默认情况下支持ES2020

在Babel REPL上检查此示例

🎉🎉更新:2019年12月🎉🎉

可选的链接提案最终在TC39委员会的2019年12月会议上达到了第4阶段。这意味着该功能将成为ECMAScript 2020标准的一部分。


4
arguments实际上不是数组。Array.prototype.slice.call(arguments)将其转换为正式数组。学习
deefour 2012年

23
this'd是很多更有效的var obj = arguments[0];,并开始从var i = 1不是复制的arguments对象
克劳迪乌

2
为了节俭起见,我将一个版本与try / catch组合在一起,这并不奇怪-性能太差了(出于某些原因,在Safari中除外)。下面有一些相当不错的答案,还有克劳迪乌(Claudiu)的修改,它的表现也明显好于所选答案。在此处查看jsperf jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica 2014年

3
在ES6中,args可以删除变量声明,并将...args 其用作该checkNested方法的第二个参数。developer.mozilla.org/en/docs/Web/JavaScript/Reference/…–
Vernon

6
这是非常难以维持的。如果有任何属性键发生变化(他们将会),则项目中的所有开发人员都必须对整个代码库进行“字符串搜索”。这实际上不是解决问题的方法,因为它引入了更大的问题
Drenai

356

这是我从Oliver Steele挑选的一种模式:

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

实际上,整篇文章都是关于如何在javascript中执行此操作的讨论。他决定使用上面的语法(一旦习惯就不难读)作为习语。


8
@wared我认为它的简洁性很有趣。在链接的帖子中有关于性能特征的详细讨论。是的,它始终执行所有测试,但是避免了创建临时变量,如果希望避免每次创建新的空对象的开销,可以将{}别名为变量。在99%的情况下,我认为速度并不重要,在这种情况下,无法替代性能分析。
·穆阿特

9
@MuhammadUmer不,要点(test || {})是,如果未定义测试,则说明您在做({}.level1 || {})。当然,它{}.level1是未定义的,所以这意味着您正在执行{}.level2,依此类推。
Joshua Taylor

3
@JoshuaTaylor:我认为他的意思是如果test未声明,将出现ReferenceError,但这不是问题,因为如果未声明,则有一个错误需要修复,因此错误是一件好事。

32
你说“这是不是,一旦习惯它”。好吧,这些迹象表明您已经知道这是一团糟。那为什么要提出这个解决方案呢?它容易出现错别字,并且绝对不会提高可读性。看看吧!如果我必须写一条丑陋的台词,那应该是可读的。所以我要坚持if(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky

8
除非我缺少什么,否则这对于可能是错误的布尔型最终属性将不起作用……可悲的是。否则我喜欢这个成语。
T3db0t

261

更新资料

看起来lodash _.get为您的所有嵌套属性添加了需求。

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


先前的答案

lodash用户可能会喜欢lodash.contrib,它有几种方法可以缓解此问题

getPath

签名: _.getPath(obj:Object, ks:String|Array)

根据给定键描述的路径获取嵌套对象中任意深度的值。键可以数组或点分隔的字符串形式给出。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

Lodash确实需要_.isPathDefined(obj,pathString)方法。
马修·佩恩

@MatthewPayne也许会很好,但是确实没有必要。您可以非常轻松地自己完成操作function isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no,2015年

11
Lodash本身具有相同的功能:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom

更好的是_.result
Shishir Arora

如果您需要确定多个不同的路径,请考虑: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison

209

我已经针对此问题提出的一些建议进行了性能测试(感谢cdMinix添加lodash),结果如下所示。

免责声明#1将字符串转换为引用是不必要的元编程,并且最好避免。首先,不要忘记您的参考。从这个类似问题的答案中了解更多信息

免责声明2我们在这里谈论每毫秒的数百万次操作。在大多数用例中,所有这些都不太可能产生很大的变化。在了解每种限制的情况下选择最有意义的一种。对我来说,我会reduce出于方便的考虑而去。

对象包裹(Oliver Steele撰写) – 34%–最快

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

原始解决方案(建议) – 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 }
}

validChain – 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);

16
应该注意的是,测试所占的百分比越多-越慢
avalanche1

2
lodash _.get()呢?与这些答案相比,表现如何?
beniutek '17

1
根据情况,这些方法中的每种方法都比其他方法慢或快。如果找到了所有键,则“对象包装”可能是最快的,但是如果找不到键之一,则“本机解决方案/原始解决方案”可能会更快。
evilReiko

1
@evilReiko如果未找到任何键,但任何方法都将变慢,但按比例计算,它仍然几乎相同。但是,您是对的–这比其他任何事情都更加明智。我们在这里谈论的是每毫秒百万次迭代。我认为没有用例会带来很大的不同。我个人而言,我会为了方便reducetry/catch出于方便。
unitario

try { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

如果您像字符串一样处理名称,则可以在任何深度读取对象属性'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


3
值得注意的是,至少在Chrome中,此方法非常有效,在某些情况下,其性能优于所选答案的@Claudiu修改版本。在此处查看性能测试:jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica 2014年

28
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 a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

如果您在ES6环境中(或使用6to5)进行编码,则可以利用arrow函数语法:

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

关于性能,try..catch如果设置了属性,则使用block 不会造成性能损失。如果未设置该属性,则会对性能产生影响。

考虑简单地使用_.has

var object = { 'a': { 'b': { 'c': 3 } } };

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true

2
我认为该try-catch方法是最佳答案。在查询对象的类型与假设该API存在之间存在哲学上的区别,如果不存在则相应地失败。后者更适合于松散类型的语言。请参阅stackoverflow.com/a/408305/2419669。这种try-catch方法也比清楚得多if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }
yangmillstheory

24

怎么样

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}

15
我不认为try / catch是测试对象是否存在的好方法:try / catch的目的是处理异常,而不是正常条件(例如此处的测试)。我认为每一步(typeof foo ==“ undefined”)都更好-通常,如果要使用如此深层嵌套的属性,可能需要进行一些重构。此外,如果引发异常,则try / catch会导致Firebug(以及打开错误中断功能的任何浏览器)中断。
Sam Dutton 2010年

我对此表示投票,因为如果您使用其他解决方案,浏览器将检查是否存在。假设您要呼叫“ acb = 2”。浏览器必须在修改值之前检查其是否存在(否则将是操作系统捕获的内存错误)。

4
问题仍然存在:对于浏览器来说,设置一次try catch或hasOwnProperty()n次调用会更快吗?

14
为什么又变坏了?对我来说这看起来最干净。
奥斯汀祈祷2014年

我会说:如果您希望该属性存在,则可以将其包装到try块中。如果不存在,则为错误。但是,如果您只是懒惰,并且在属性不存在的情况下将常规代码放入catch块,则会滥用try / catch。这里需要if / else或类似的东西。
罗布施(Robsch)2016年

18

ES6答案,经过全面测试:)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→查看Codepen的完整测试范围


我使您的测试失败,将flat prop的值设置为0。您必须关心类型强制。
Germain '18

@germain 对您有用吗?(我明确比较===了不同的falsys,并增加了测试。如果您有更好的主意,请告诉我)。
Frank Nocke '18 -10-2

我再次使您的测试失败,将flat prop的值设置为false。然后,您可能希望将对象中的值设置为undefined(我知道这很奇怪,但它是JS)。我设定了一个错误的正值'Prop not Found'const hasTruthyProp = prop => prop === 'Prop not found' ? false : true const path = obj => path => path.reduce((obj, prop) => { return obj && obj.hasOwnProperty(prop) ? obj[prop] : 'Prop not found' }, obj) const myFunc = compose(hasTruthyProp, path(obj))
germain '18

您能否派发我的Codepen(右上角的,简单的),更正并添加测试,并向我发送您的URL?谢谢=)
Frank Nocke '18

逃到一个(巨大的)第三方图书馆...可能,但我不喜欢。
Frank Nocke


11

我尝试了一种递归方法:

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

! keys.length ||出递归踢,因此不会跑,没有留下来测试键的功能。测试:

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';

9

我认为以下脚本给出了更具可读性的表示形式。

声明一个函数:

var o = function(obj) { return obj || {};};

然后像这样使用它:

if (o(o(o(o(test).level1).level2).level3)
{

}

我称其为“悲伤小丑技巧”,因为它使用的是符号o(


编辑:

这是TypeScript的版本

它在编译时进行类型检查(如果您使用Visual Studio这样的工具,还会进行智能检查)

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

但是这一次智能感知工作!

另外,您可以设置默认值:

o(o(o(o(o(test).level1).level2).level3, "none")

1
°0o <°(())))><
Daniel W.

1
我喜欢这个,因为它是诚实的,当您不知道您的Object类型时会在您的脸上抛出“ undefined” 。+1。

1
只要您将声明保留在原谅中,您还可以称其为快乐小丑技巧(o
Sventies

感谢Sventies。我喜欢你的评论。从一个很好的角度来看-这种情况通常用在“ ifs”中,并且总是用外部括号包围。所以,是的,它的确基本上是一个快乐的小丑:)))
VeganHunter

您真的需要爱上括号才能实现这一目标……
Bastien7

7

创建一个function整体并在整个项目中使用

尝试这个

function isExist(arg){
   try{
      return arg();
   }catch(e){
      return false;
   }
}

let obj={a:5,b:{c:5}};

console.log(isExist(()=>obj.b.c))
console.log(isExist(()=>obj.b.foo))
console.log(isExist(()=>obj.test.foo))

如果条件

if(isExist(()=>obj.test.foo)){
   ....
}

效果很好。简单高效
gbland777

6

一种简单的方法是这样的:

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)时,将捕获情况。


6

我没有看到有人使用代理的任何示例

所以我想出了自己的。很棒的事情是您不必插值字符串。您实际上可以返回一个可链接的对象函数,并用它做一些神奇的事情。您甚至可以调用函数并获取数组索引来检查深层对象

上面的代码适用于同步的东西。但是,您将如何测试诸如ajax调用之类的异步内容呢?您如何测试?如果返回500 http错误时响应不是json怎么办?

window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

确保您可以使用async / await摆脱一些回调。但是,如果您可以更神奇地做到这一点呢?看起来像这样的东西:

fetch('https://httpbin.org/get').json().headers['User-Agent']

您可能想知道所有的Promise和.thenChain都在哪里...这可能会阻塞您所知道的所有信息...但是使用具有Promise的相同Proxy技术,您实际上可以测试深层嵌套的复杂路径是否存在,而无需编写任何函数


如果有人感兴趣,我已经在npm
Endless

5

基于这个答案,我想出了一个通用的函数来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 );
}

1
这是我的最终方法function validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington

5

我创建了一个小函数来安全地获取嵌套对象的属性。

function getValue(object, path, fallback, fallbackOnFalsy) {
    if (!object || !path) {
        return fallback;
    }

    // Reduces object properties to the deepest property in the path argument.
    return path.split('.').reduce((object, property) => {
       if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {
            // The property is found but it may be falsy.
            // If fallback is active for falsy values, the fallback is returned, otherwise the property value.
            return !object[property] && fallbackOnFalsy ? fallback : object[property];
        } else {
            // Returns the fallback if current chain link does not exist or it does not contain the property.
            return fallback;
        }
    }, object);
}

或更简单但稍微不可读的版本:

function getValue(o, path, fb, fbFalsy) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);
}

甚至更短,但不会在虚假标志上回退:

function getValue(o, path, fb) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);
}

我测试了:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

这是一些测试:

// null
console.log(getValue(obj, 'c.d', 'fallback'));

// array
console.log(getValue(obj, 'c.b.c', 'fallback'));

// array index 2
console.log(getValue(obj, 'c.b.c.2', 'fallback'));

// no index => fallback
console.log(getValue(obj, 'c.b.c.10', 'fallback'));

要查看所有包含文档的代码和我尝试过的测试,可以检查我的github内容:https : //gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

@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;
}

使用类似的测试:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

唯一的问题是,如果存在多个未定义键级别,则将收到TypeError,例如checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS 2012年

1
更合适的方法是every,其值可以直接返回。
RobG

也许更改success = false;return false。一旦知道它会崩溃,就应该纾困,一旦它为null或未定义,就不会存在任何更深层次的东西。这将防止嵌套更深的项目上的错误,因为它们显然也不存在。
韦德

4

我认为这是一个小改进(变成了1线):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

之所以起作用,是因为&&运算符返回它求值的最终操作数(并使其短路)。


4

如果属性存在,我一直在寻找要返回的值,因此我通过上面的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


3

CMS给出的答案也可以很好地与以下对null检查的修改一起使用

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;
    }

3

这个答案开始阐述了以下选择。两者的树相同:

var o = { a: { b: { c: 1 } } };

未定义时停止搜索

var u = undefined;
o.a ? o.a.b ? o.a.b.c : u : u // 1
o.x ? o.x.y ? o.x.y.z : u : u // undefined
(o = o.a) ? (o = o.b) ? o.c : u : u // 1

确保每个级别一个接一个

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined

3

我知道这个问题很旧,但是我想通过将其添加到所有对象中来提供扩展。我知道人们倾向于使用对象原型来扩展对象功能,但是我发现没有比这更容易的事情了。另外,现在允许使用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;
}});

现在,为了测试任何对象中的任何属性,您可以简单地执行以下操作:

if( obj.has("some.deep.nested.object.somewhere") )

这是一个jsfiddle进行测试,特别是它包含一些jQuery,如果您直接修改Object.prototype会由于该属性变得可枚举而中断。这应该与第三方库一起正常工作。


3

这适用于所有对象和数组:)

例如:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {
    //do something
}

这是我对Brian答案的改进版本

我使用_has作为属性名称,因为它可能与现有的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;
}});

这是小提琴


3

这是我的看法-大多数此类解决方案都忽略嵌套数组的情况,如下所示:

    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));


3

现在我们还可以使用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)


3

您可以使用递归函数来做到这一点。即使您不知道所有嵌套对象键的名称,这也将起作用。

function FetchKeys(obj) {
    let objKeys = [];
    let keyValues = Object.entries(obj);
    for (let i in keyValues) {
        objKeys.push(keyValues[i][0]);
        if (typeof keyValues[i][1] == "object") {
            var keys = FetchKeys(keyValues[i][1])
            objKeys = objKeys.concat(keys);
        }
    }
    return objKeys;
}

let test = { level1: { level2: { level3: "level3" } } };
let keyToCheck = "level2";
let keys = FetchKeys(test); //Will return an array of Keys

if (keys.indexOf(keyToCheck) != -1) {
    //Key Exists logic;
}
else {
    //Key Not Found logic;
}

2

在codeabode(safeRead)上有一个函数可以安全地执行此操作...即

safeRead(test, 'level1', 'level2', 'level3');

如果任何属性为null或未定义,则返回一个空字符串


我有点喜欢这种带有模板的方法,因为如果未设置,它将返回一个空字符串
Lounge9,2016年

2

根据先前的评论,这是另一个版本,其中也无法定义主要对象:

// Supposing that our property is at first.second.third.property:
var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;

2

我编写了自己的函数,该函数采用了所需的路径,并具有好坏的回调函数。

function checkForPathInObject(object, path, callbackGood, callbackBad){
    var pathParts = path.split(".");
    var currentObjectPath = object;

    // Test every step to see if it exists in object
    for(var i=0; i<(pathParts.length); i++){
        var currentPathPart = pathParts[i];
        if(!currentObjectPath.hasOwnProperty(pathParts[i])){
            if(callbackBad){
                callbackBad();
            }
            return false;
        } else {
            currentObjectPath = currentObjectPath[pathParts[i]];
        }
    }

    // call full path in callback
    callbackGood();
}

用法:

var testObject = {
    level1:{
        level2:{
            level3:{
            }
        }
    }
};


checkForPathInObject(testObject, "level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good

checkForPathInObject(testObject, "level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad

我很公平地将您的代码用于我的回答
davewoodhall

2
//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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.