Тест на наявність вкладеного об’єктного ключа 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);
}

але мені було цікаво, чи є кращий спосіб.


1
ви можете перевірити дотичне питання, яке було задано нещодавно stackoverflow.com/questions/2525943/…
Anurag


Пара пропозицій там: stackoverflow.com/a/18381564/1636522
лист

Ваш поточний підхід має потенційну проблему , якщо level3 властивість є помилковим, в цьому випадку, навіть якщо властивість EXIST буде РЕТУР nfalse поглянути на цьому прикладі , будь ласка jsfiddle.net/maz9bLjx
GibboK

10
просто ви можете також спробувати ловити також
Raghavendra

Відповіді:


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 підтримується за замовчуванням

Перевірте цей приклад на вабельній REPL.

🎉🎉УПОДАТ: грудень 2019 🎉🎉

Необов'язкова пропозиція ланцюга, нарешті, досягла 4 етапу на засіданні комітету TC39 у грудні 2019 року. Це означає, що ця функція буде частиною стандарту ECMAScript 2020 .


4
argumentsнасправді не є масивом. Array.prototype.slice.call(arguments)перетворює його у формальний масив. Дізнайтеся
deefour

23
це було б набагато ефективніше зробити 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/uk/docs/Web/JavaScript/Reference/…
Вернон

7
Це дуже неможливо. Якщо якісь ключі властивостей зміняться (вони будуть), всі розробники проекту повинні будуть "шукати рядки" всієї кодової бази. Це насправді не вирішення проблеми, оскільки воно вводить набагато більшу проблему
Дренай

357

Ось шаблон, який я взяв від Олівера Стіла :

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

Насправді вся ця стаття - це обговорення того, як це можна зробити в JavaScript. Він зупиняється на використанні вищевказаного синтаксису (який не так важко прочитати, коли ви звикнете до нього) як ідіоми.


8
@wared Я думаю, що це цікаво здебільшого тим, наскільки це стисло. У викладеному пості є детальне обговорення характеристик виконання. Так, це завжди робить усі тести, але це дозволяє уникнути створення тимчасових змін, і ви можете псевдонімувати {} до var, якщо ви хочете щоразу запобігти накладному створенню нового порожнього об'єкта. У 99% випадків я не очікував, що швидкість має значення, а у випадках, коли це відбувається, немає заміни профілюванню.
Гейб Мутхарт

9
@MuhammadUmer Ні, справа в (test || {})тому, що якщо тест не визначений, то ти робиш ({}.level1 || {}). Звичайно, {}.level1не визначено, так що це означає, що ти робиш {}.level2і так далі.
Джошуа Тейлор

3
@JoshuaTaylor: Я думаю, що він означає, що якщо testвін не оголошений, з'явиться ReferenceError , але це не проблема, тому що якщо він не оголошений, виправлена ​​помилка, тому помилка - це гарна річ.

34
Ви сказали «який не є , що важко читати , як тільки ви звикнете до нього» . Ну, це знаки, ви вже знаєте, що це безлад . Тоді навіщо пропонувати таке рішення? Він схильний до друкарських помилок і не дає абсолютно нічого читабельності. Подивіться! Якщо мені доведеться написати некрасивий рядок, він повинен бути зрозумілим ; так що я збираюся просто дотримуватисяif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Шаркі

8
Якщо я щось не пропускаю, це не спрацює для бульних кінцевих властивостей, які можуть бути помилковими ... на жаль. Інакше я люблю цю ідіому.
T3db0t

261

Оновлення

Схоже, лодаш додав _.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

Лодашу дійсно потрібен метод _.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
Том

ще краще буде _.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 }
}

дійсний ланцюг - 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));
}

вкладеніPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_.get - 72%

найглибший - 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

сумні клоуни - 100% - найповільніші

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

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);

16
слід зазначити , що чим більше% тест має - тим повільніше він
avalanche1

2
що з лодаш _.get()? наскільки це ефективно у порівнянні з цими відповідями?
beniutek

1
Кожен метод цих методів є повільнішим або швидшим, ніж інші, залежно від ситуації. Якщо всі ключі знайдені, то "Об'єкт обертання" міг би бути найшвидшим, але якщо одного з ключів не знайдено, то "Нативне рішення / оригінальне рішення" може бути швидшим.
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.


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

2
Я думаю, що try-catchпідхід - найкраща відповідь. Існує філософська різниця між запитом об'єкта на його тип і припущенням існування API та відмовою відповідно, якщо цього немає. Остання більш доречна в мовах, що друкуються слабко. Дивіться stackoverflow.com/a/408305/2419669 . try-catchПідхід також набагато ясніше , ніж if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory

24

як щодо

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

}

15
Я не думаю, що "пробувати / ловити" є хорошим способом перевірити наявність об'єкта: спроба "ловити" має на увазі обробку винятків, а не нормальних умов, таких як тест. Я думаю (typeof foo == "undefined") на кожному кроці краще - і взагалі, мабуть, потрібен якийсь рефакторинг, якщо ви працюєте з такими глибоко вкладеними властивостями. Крім того, спробу / catch спричинить перерву в Firebug (і в будь-якому браузері, де вмикається помилка помилки), якщо буде викинуто виняток.
Сем Даттон

Я голосую за це, тому що браузер перевірятиме існування двічі, якщо ви використовуєте інші рішення. Скажімо, ви хочете зателефонувати на "acb = 2". Браузер повинен перевірити існування перед тим, як змінити значення (інакше це буде помилка пам'яті, виявлена ​​ОС).

4
Залишається питання: відьом швидше браузери встановити спробу лову чи дзвінка hasOwnProperty()n разів?

14
Чому це знову погано? Це для мене виглядає найчистіше.
Остін Моліться

Я б сказав: Якщо ви очікуєте, що властивість існує, тоді це добре, щоб зафіксувати її у блоці спробу. Якщо його тоді не існує, це помилка. Але якщо ви просто ледачі та вкладаєте звичайний код у блок лову для випадку, якщо властивість не існує, спробуй / зловитись, неправильно використати. Тут потрібен if / else чи щось подібне.
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 разом із вавилем 7 - tc39-пропозиція-факультативно-ланцюгова

Код виглядатиме так:

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

Зауважте, що цей синтаксис майже напевно зміниться, оскільки деякі учасники 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';

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

але цього разу інтелігенція працює!

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

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

1
°0o <°(())))><
Даніель В.

1
Мені це подобається, тому що він чесний і кидає вам "невизначене" в обличчя, коли ви не знаєте свого Objectтипу. +1.

1
Поки ви зберігаєте заяву в паренах, ви можете також називати це технікою щасливого клоуна (o
Sventies

Спасибі Свенти. Я люблю ваш коментар. На це досить приємно виглядати - такі умови здебільшого використовуються у "ifs" та завжди оточені зовнішніми дужками. Отже, так, це здебільшого щасливий клоун дійсно :)))
VeganHunter

Вам справді потрібно полюбити дужки, щоб піти на цю ...
Bastien7

7

створити глобальний function і використовувати в цілому проект

спробуйте це

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

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

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

якщо стан

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

Чудово працює. Просте та ефективне
gbland777

6

Один простий спосіб такий:

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

try/catchВловлює випадки , коли для будь - якої з більш високого рівня об'єктів , таких як тест, test.level1, test.level1.level2 не визначені.


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 / очікувати, щоб позбутися деяких зворотних дзвінків. Але що робити, якщо ви могли зробити це ще чарівно? щось виглядає так:

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

Вам, напевно, цікаво, де всі обіцянки та .thenланцюги ... це може блокувати все, що ви знаєте ... але, використовуючи ту саму техніку проксі з обіцянкою, ви можете насправді випробувати глибоко вкладений складний шлях для її існування, не записуючи жодної функції


Якщо когось цікавить, я опублікував версію async в 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

Єдине питання з цим полягає в тому, якщо є кілька рівнів невизначених ключів, тоді ви отримуєте TypeError, наприклад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 як ім'я властивості, оскільки воно може суперечити існуючому має властивість (наприклад: 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

Ось мій погляд на це - більшість цих рішень ігнорує випадок вкладеного масиву, як у:

    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

тут функція на codecodebode (safeRead), яка зробить це безпечним чином ... тобто

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

якщо будь-яке властивість є нульовим або невизначеним, повертається порожня рядок


Мені подобається цей метод із шаблонуванням, оскільки він повертає порожню рядок, якщо не встановлено
Lounge9

2

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

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

2

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

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

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

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

Використання:

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


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

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

Я хоч справедлива, щоб дати вам
подяку

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.