Проверка наличия вложенного ключа объекта 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 ответов


вы должны сделать это шаг за шагом, если вы не хотите, чтобы 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;
}

_.get – 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 блок, если свойство имеет значение. Существует влияние на производительность, если свойство unset.

рассмотрим просто с помощью _.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));
}

на ! 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
}

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


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

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

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

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

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

}

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


EDIT:

вот версия машинопись

Он дает проверки типа во время компиляции (а также 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, когда он возвращает ошибку 500 http?

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-предложение-необязательно-цепочка

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

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

прекратить поиск, когда не определено

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, который ломается, если вы изменяете объект.прототип непосредственно из-за свойства становится перечисляемым. Это должно отлично работать с библиотеками 3rd party.


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

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

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


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

ex:

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

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

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

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
}

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

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

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


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

// 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 выше. Вот что я придумал:--2-->

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

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