Проверка существования вложенного ключа объекта JavaScript - PullRequest
553 голосов
/ 13 апреля 2010

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

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

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

Ответы [ 53 ]

3 голосов
/ 16 июля 2015

Мне кажется, это небольшое улучшение (становится 1-строчным):

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

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

3 голосов
/ 25 июля 2015

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

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

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

например:

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

это моя улучшенная версия ответа Брайана

Я использовал _has в качестве имени свойства, поскольку оно может конфликтовать с существующим свойством 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;
}});

Вот скрипка

3 голосов
/ 30 апреля 2017

Теперь мы также можем использовать 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)
2 голосов
/ 10 июля 2015
//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
2 голосов
/ 19 марта 2015

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

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
2 голосов
/ 13 апреля 2013

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

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

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

2 голосов
/ 14 сентября 2016

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

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]);
  }
});
2 голосов
/ 28 августа 2013

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

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