Тест на наличие вложенного ключа объекта JavaScript


если у меня есть ссылка на объект:

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

но мне было интересно, если есть лучший способ.

30 468

30 ответов:

вы должны сделать это шаг за шагом, если вы не хотите, чтобы TypeError, потому что если один из членов null или undefined, и вы пытаетесь получить доступ к члену исключение будет выдано.

можно просто 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

вот шаблон I снято с Оливера Стила:

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

на самом деле вся эта статья-это обсуждение того, как вы можете сделать это в javascript. Он решает использовать приведенный выше синтаксис (который не так уж трудно прочитать, как только вы привыкнете к нему) в качестве идиомы.

обновление

похоже на лодаш добавил_.get для всех ваших вложенных потребностей в получении свойств.

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

https://lodash.com/docs#get


предыдущий ответ

лодашь пользователи могут пользоваться лодашь.контриб С пару методов, которые смягчают эту проблему.

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

я сделал тесты производительности (спасибо cdMinix для добавления lodash) по некоторым предложениям, предложенным к этому вопросу с результатами, перечисленными ниже.

отказ от ответственности #1 превращение строк в ссылки является ненужным метапрограммированием и, вероятно, лучше избегать. Не теряйте из виду ваши ссылки для начала. Прочитайте больше из этого ответа на аналогичный вопрос.

отказ от ответственности #2 здесь речь идет о миллионах операций в миллисекунду. Очень маловероятно, что любой из них будет делать большой разницы в большинстве случаев. Выберите то, что имеет наибольший смысл, зная ограничения каждого из них. Для меня я бы пошел с чем-то вроде reduce из удобства.

объект Wrap (Оливер Стил) – 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;
}

_.получить – 72%

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

вы можете прочитать свойство объекта на любой глубине, если вы обрабатываете имя как строку:'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 a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

если вы кодируете в среде ES6 (или используете 6to5), то вы можете воспользоваться функции стрелочку синтаксис:

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

Что касается производительности, нет никакого штрафа за использование try..catch блок, если свойство имеет значение. Если свойство не задано, это влияет на производительность.

рассмотрим просто с помощью _.has:

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

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

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

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

как о

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

}

я попробовал рекурсивный подход:

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

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

ES6 ответ, тщательно протестирован :)

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

читайте:Codepen с полным тестовым покрытием

один простой способ заключается в следующем:

try {
    alert(test.level1.level2.level3);
} catch(e) {
    alert("undefined");    // this is optional to put any output here
}

The try/catch ловит случаи, когда любой из объектов более высокого уровня, таких как test, test.уровень 1, Тест.уровень 1.Уровень 2 не определен.

Я думаю, что следующий скрипт дает более читаемое представление.

объявить функцию:

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

тогда используйте его так:

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

}

я называю это "техника грустного клоуна", потому что он использует знак o(


EDIT:

вот версия TypeScript

Он дает проверки типа во время компиляции (а также intellisense, если вы используете такой инструмент, как 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

но на этот раз IntelliSense работает!

плюс, вы можете установить значение по умолчанию:

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

Я не видел ни одного примера кто-то использует Прокси

так что я придумал свой собственный. Самое замечательное в этом то, что вам не нужно интерполировать строки. Вы можете на самом деле вернуть цепочку-в состоянии объект функция и сделать некоторые магические вещи с ним. Вы даже можете вызывать функции и получать индексы массива для проверки глубоких объектов

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

приведенный выше код отлично работает для синхронных вещей. Но как бы вы проверьте что-то асинхронное, как этот вызов ajax? Как вы это проверяете? что делать, если ответ не json, когда он возвращает ошибку http 500?

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

вы, наверное, задаетесь вопросом, где все обещание & .then цепи... это может быть блокировкой для всего, что вы знаете... но, используя тот же метод прокси-с обещайте, что вы действительно можете проверить глубоко вложенный сложный путь для его существования, никогда не написав ни одной функции

function resolve(target) { 
  return new Proxy(() => {}, {
    get(noop, key) {
      return key === 'then' ? target.then.bind(target) : resolve(
        Promise.resolve(target).then(target => {
          if (typeof target[key] === 'function') return target[key].bind(target)
          return target[key]
        })
      )
    },

    apply(noop, that, args) {
      return resolve(target.then(result => {
        return result.apply(that, args)
      }))
    },
  })
}

// this feels very much synchronous but are still non blocking :)
resolve(window) // this will chain a noop function until you call then()
  .fetch('https://httpbin.org/get')
  .json()
  .headers['User-Agent']
  .then(console.log, console.warn) // you get a warning if it doesn't exist
  
// You could use this method also for the first test object
// also, but it would have to call .then() in the end



// Another example
resolve(window)
  .fetch('https://httpbin.org/get?items=4&items=2')
  .json()
  .args
  .items
  // nice that you can map an array item without even having it ready
  .map(n => ~~n * 4) 
  .then(console.log, console.warn) // you get a warning if it doesn't exist

более короткая, ES5 версия отличного ответа @CMS:

// 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

на основе ответ, Я придумал эту общую функцию с помощью 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 );
}

вы также можете использовать tc39 дополнительное предложение цепочки вместе с babel 7 - tc39-proposal-optional-chaining

код будет выглядеть так:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);

ответ, данный 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;
    }

были разработаны следующие варианты, начиная с ответ. Одно и то же дерево для обоих :

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

остановить поиск, когда значение undefined

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

Я знаю, этот вопрос старый, но я хотел бы предложить расширение, добавив ко всем объектам. Я знаю, что люди склонны хмуриться при использовании прототипа объекта для расширенной функциональности объекта, но я не нахожу ничего проще, чем делать это. Кроме того, теперь это разрешено с "объект".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, который ломается, если вы изменяете объект.прототип непосредственно из-за свойства становится перечислимым. Это должно хорошо работать с библиотеками сторонних производителей.

Я думаю, что это небольшое улучшение (становится 1-liner):

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

это работает, потому что оператор && возвращает конечный операнд, который он оценил (и это короткое замыкание).

это работает со всеми объектами и массивами :)

ex:

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

Это моя улучшенная версия ответа

Я однако других как имя свойства, потому что оно может конфликтовать с существующим имеет свойство (например: карты)

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

здесь скрипка

вот мой взгляд на это - большинство из этих решений учитывать регистр вложенного массива, как в:

    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)

Я подумал, что добавлю еще один, который я придумал сегодня. Причина, по которой я горжусь этим решением, заключается в том, что он избегает вложенных скобок, которые используются во многих решениях, таких как объект Wrap (Оливер Стил):

(в этом примере я использую символ подчеркивания в качестве переменной переменную, но любое имя переменной будет работать)

//the 'test' object
var test = {level1: {level2: {level3: 'level3'}}};

let _ = test;

if ((_=_.level1) && (_=_.level2) && (_=_.level3)) {

  let level3 = _;
  //do stuff with level3

}

//you could also use 'stacked' if statements. This helps if your object goes very deep. 
//(formatted without nesting or curly braces except the last one)

let _ = test;

if (_=_.level1)
if (_=_.level2)
if (_=_.level3) {

   let level3 = _;
   //do stuff with level3
}


//or you can indent:
if (_=_.level1)
  if (_=_.level2)
    if (_=_.level3) {

      let level3 = _;
      //do stuff with level3
}

есть функция здесь на thecodeabode (safeRead) который будет делать это безопасным способом... то есть

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

если какое-либо свойство имеет значение null или не определено, возвращается пустая строка

на основе предыдущий комментарий, вот еще одна версия, где главный объект не может быть определен как:

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

Я написал свою собственную функцию, которая принимает нужный путь, и имеет хорошую и плохую функцию обратного вызова.

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
//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

Я искал значение, которое будет возвращено, если свойство существует, поэтому я изменил ответ 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

у меня была та же проблема, и я хотел посмотреть, смогу ли я придумать свое собственное решение. Это принимает путь, который вы хотите проверить в виде строки.

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

вот фрагмент с некоторыми журналирования и тестовых случаев:

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

небольшое изменение в ответ разрешить вложенные массивы в пути

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

Проверьте связанный ответ для использования :)