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

692

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

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

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

user113716
источник
1
Вы можете проверить вопрос, связанный с тангенциальной связью, который был задан недавно stackoverflow.com/questions/2525943/…
Anurag,
См. Также stackoverflow.com/questions/10918488/…
Джеймс МакМахон
Там есть пара предложений: stackoverflow.com/a/18381564/1636522
лист
В вашем текущем подходе есть потенциальная проблема, если свойство level3 имеет значение false, в этом случае, даже если свойство будет возвращаться, nfalse посмотрите на этот пример, пожалуйста, jsfiddle.net/maz9bLjx
GibboK
10
просто вы можете использовать попробовать поймать также
Рагхавендра

Ответы:

489

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

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 

Если какой-либо из уровней, к которым осуществляется доступ, является nullилиundefined выражение будет разрешеноundefined само по себе.

Предложение также позволяет безопасно обрабатывать вызовы методов:

obj?.level1?.method();

Выше выражение будет производить , undefinedесли obj, obj.level1или obj.level1.methodявляются nullилиundefined , иначе это вызовет функцию.

Вы можете начать играть с этой функцией с Babel, используя дополнительный плагин цепочки .

Начиная с Babel 7.8.0 , ES2020 поддерживается по умолчанию

Проверьте этот пример на Babel REPL.

«ОБНОВЛЕНИЕ: декабрь 2019»

Факультативное предложение о цепочке, наконец, достигло стадии 4 на заседании комитета TC39 в декабре 2019 года. Это означает, что эта функция будет частью стандарта ECMAScript 2020 .

CMS
источник
4
argumentsна самом деле не массив. Array.prototype.slice.call(arguments)преобразует его в формальный массив. Learn
deefour
23
this'd быть намного более эффективным , чтобы сделать var obj = arguments[0];и начать var i = 1вместо того , чтобы копировать argumentsобъект
Клаудиу
2
Я собрал версию с try / catch для экономии, и неудивительно - производительность ужасная (за исключением Safari по некоторым причинам). Ниже приведены некоторые ответы, которые довольно эффективны, наряду с модификацией Клаудиу, которая также значительно более эффективна, чем выбранный ответ. Смотрите jsperf здесь. Jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica
3
В ES6 argsобъявление переменной может быть удалено и ...args может использоваться как второй аргумент checkNestedметода. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Вернон
7
Это очень неприемлемо. Если какие-либо ключи свойств изменятся (они будут), все разработчики проекта должны будут «искать строки» по всей базе кода. На самом деле это не решение проблемы, так как оно представляет гораздо большую проблему
Drenai
357

Вот образец, который я взял от Оливера Стила :

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

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

Гейб Моутарт
источник
8
@ Wared Я думаю, что это интересно в основном потому, что это лаконично. Подробное обсуждение характеристик производительности в связанном посте. Да, он всегда выполняет все тесты, но он избегает создания временных переменных, и вы можете использовать псевдоним {} для переменной, если вы хотите избежать накладных расходов на создание нового пустого объекта каждый раз. В 99% случаев я не ожидал бы, что скорость имеет значение, а в тех случаях, когда это имеет значение, нет никакой замены для профилирования.
Гейб Моутарт
9
@MuhammadUmer Нет, суть в (test || {})том, что если тест не определен, то вы делаете ({}.level1 || {}). Конечно, {}.level1не определено, так что это означает, что вы делаете {}.level2, и так далее.
Джошуа Тейлор
3
@JoshuaTaylor: Я думаю, он имеет в виду, что если testне объявлено, то будет ReferenceError , но это не проблема, потому что, если он не объявлен, есть ошибка, которую нужно исправить, поэтому ошибка - это хорошо.
34
Вы сказали «что не так сложно читать, как только вы к этому привыкнете » . Ну, эти признаки вы знаете , уже это беспорядок . Тогда зачем предлагать это решение? Он склонен к опечаткам и абсолютно ничего не дает читабельности. Просто посмотри! Если мне нужно написать некрасивую строку, она должна быть читабельной ; так что я собираюсь просто придерживаться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
11
У Lodash есть такая же функциональность:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom
еще лучше было бы _.result
Арора
Если вам нужно определить несколько разных путей, подумайте: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Саймон Хатчисон
210

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

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

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

Обертывание объекта (Оливер Стил) - 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);
unitario
источник
16
следует отметить, что чем больше тестов - тем МЕДЛЕННО
лавина 1
2
что насчет Лодаша _.get()? насколько он эффективен по сравнению с этими ответами?
beniutek
1
Каждый из этих методов медленнее или быстрее, чем другие, в зависимости от ситуации. Если все ключи найдены, «Object Wrap» может быть самым быстрым, но если один из ключей не найден, то «Native Solution / Original Solution» может быть быстрее.
evilReiko
1
@evilReiko Любой метод будет медленнее, если ключи не найдены, но в пропорции друг к другу он все еще почти одинаков. Однако вы правы - это скорее интеллектуальное упражнение, чем что-либо еще. Мы говорим о миллионе итераций в миллисекунду здесь. Я не вижу ни одного случая, когда это имело бы большое значение. Я лично, я бы пошел для reduceили try/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.

Kennebec
источник
3
Стоит отметить, что этот метод очень эффективен, по крайней мере в Chrome, в некоторых случаях он превосходит модифицированную @Claudiu версию выбранного ответа. Смотрите тест производительности здесь: jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica
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 ), вы можете воспользоваться синтаксисом функции стрелки :

// 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
Gajus
источник
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

}
user187291
источник
15
Я не думаю, что try / catch является хорошим способом проверки существования объекта: try / catch предназначен для обработки исключений, а не обычных условий, таких как проверка здесь. Я думаю (typeof foo == "undefined") на каждом шаге лучше - и в целом, вероятно, требуется некоторый рефакторинг, если вы работаете с такими глубоко вложенными свойствами. Кроме того, try / catch вызовет перерыв в Firebug (и в любом браузере, где включена функция break-on-error), если возникнет исключение.
Сэм Даттон
Я голосую за это, потому что браузер дважды проверит существование, если вы используете другие решения. Допустим, вы хотите позвонить «acb = 2». Браузер должен проверить существование перед изменением значения (иначе это будет ошибка памяти, обнаруженная ОС).
4
Вопрос все еще остается: ведь браузеры быстрее устанавливают функцию try catch или вызывают hasOwnProperty()n раз?
14
Почему это снова плохо? Это выглядит чистым для меня.
Остин Прай
Я бы сказал: если вы ожидаете, что свойство существует, тогда можно обернуть его в блок try. Если этого не существует, то это ошибка. Но если вы просто ленивый и вставляете обычный код в блок catch для случая, когда свойство не существует, try / catch используется неправильно. Здесь, если / иначе или что-то подобное требуется.
robsch
18

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

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

→ см. Codepen с полным тестовым покрытием

Фрэнк Нок
источник
Я сделал ваши тесты не удалось установить значение плоской опоры на 0. Вы должны заботиться о принуждении типа.
Жермен
@germain Это работает для вас? (Я явно сравниваю ===для разных фальшивок и добавил тест. Если у вас есть идея получше, дайте мне знать).
Фрэнк Нок
Я сделал ваши тесты неудачными снова, установив значение плоской опоры в 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
Можете ли вы раскошелиться на мой кодовый блок (вверху справа, просто), исправить и добавить тесты и отправить мне свой URL? Спасибо =)
Фрэнк Нок
Убегать в (огромную) стороннюю библиотеку ... возможно, но не мои предпочтения.
Фрэнк Нок
17

Вы также можете использовать дополнительное предложение о связывании tc39 вместе с babel 7 - tc39-offer-option-chaining

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

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);
Goran.it
источник
Обратите внимание, что этот синтаксис почти наверняка изменится, так как некоторые члены TC39 имеют возражения.
Jhpratt GOFUNDME RELICENSING
Возможно, но это будет доступно в какой-то форме вовремя, и это единственное, что имеет значение. Это одна из особенностей, которые мне больше всего не хватает в JS.
Goran.it
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';
jrode
источник
9

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

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

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

затем используйте это так:

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

}

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


РЕДАКТИРОВАТЬ:

вот версия для 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")
VeganHunter
источник
1
°0o <°(())))><
Даниэль В.
1
Мне нравится этот, потому что это честно и бросает "неопределенный" в ваше лицо, когда вы не знаете свой Objectтип. +1.
1
Пока вы держите заявление в паранах, вы можете также называть его техникой счастливого клоуна (o
Sventies
Спасибо Sventies. Мне нравится ваш комментарий. Это довольно хороший взгляд - такие условия чаще всего используются в «if» и всегда заключаются в внешние скобки. Так что, да, это в основном счастливый клоун :)))
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.level1, test.level1.level2 не определены.

jfriend00
источник
6

Я не видел ни одного примера использования прокси

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

Приведенный выше код отлично работает для синхронных вещей. Но как бы вы протестировали что-то асинхронное, как этот вызов 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цепочки ... это может блокировать все, что вы знаете ... но, используя ту же технику 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 }
Джеймс Харрингтон
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 gist: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

В. Самбор
источник
4

Короче, 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
mikemaccana
источник
единственная проблема с этим, если есть несколько уровней неопределенных ключей, тогда вы получаете ошибку типа, напримерcheckObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS
1
Более подходящий метод - каждый , чье значение может быть возвращено напрямую.
RobG
Может быть, изменить success = false;на return false. Вы должны выручить, как только вы узнаете, что он сломался, ничего более глубокого не может существовать, если он нулевой или неопределенный. Это предотвратит ошибки в более глубоких вложенных элементах, поскольку они, очевидно, тоже не существуют.
Брод
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, отлично работает со следующей модификацией для нулевых проверок, а также

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

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

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

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

Юлиус Муссо
источник
Вы буквально скопировали то, что, как они сказали, они обычно делают, и хотите избежать ...
Шон Кендл
3

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

например:

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

Вот скрипка

adutu
источник
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

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

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

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

Бен
источник
Мне нравится этот метод с использованием шаблонов, потому что он возвращает пустую строку, если не установлен
Lounge9
2

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

// Supposing that our property is at first.second.third.property:
var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;
Juampy NR
источник
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
Стефан Лафлеш
источник
Хотя я честен, чтобы
поблагодарить
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
Alejandro
источник