Test na obecność zagnieżdżonego klucza obiektu JavaScript


691

Jeśli mam odwołanie do obiektu:

var test = {};

które potencjalnie (ale nie natychmiast) zagnieżdżą obiekty, takie jak:

{level1: {level2: {level3: "level3"}}};

Jaki jest najlepszy sposób sprawdzenia istnienia własności w głęboko zagnieżdżonych obiektach?

alert(test.level1);daje undefined, ale alert(test.level1.level2.level3);zawodzi.

Obecnie robię coś takiego:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

ale zastanawiałem się, czy jest lepszy sposób.


1
możesz sprawdzić stycznie powiązane pytanie, które zostało ostatnio zadane stackoverflow.com/questions/2525943/…
Anurag



W twoim obecnym podejściu istnieje potencjalny problem, jeśli właściwość level3 jest fałszywa, w takim przypadku, nawet jeśli właściwość istnieje, powróci nfalse spójrz na ten przykład, proszę jsfiddle.net/maz9bLjx
GibboK

10
po prostu możesz użyć try catch również
Raghavendra

Odpowiedzi:


488

Musisz to zrobić krok po kroku, jeśli nie chcesz, TypeErrorponieważ jeśli jeden z członków jest nulllub undefinedi próbujesz uzyskać dostęp do członka, zostanie zgłoszony wyjątek.

Możesz albo po prostu catchwyjątek, albo utworzyć funkcję testującą istnienie wielu poziomów, coś w tym rodzaju:

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

AKTUALIZACJA ES6:

Oto krótsza wersja oryginalnej funkcji, wykorzystująca funkcje i rekurencję ES6 (ma również odpowiednią formę wywołania ogona ):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

Jeśli jednak chcesz uzyskać wartość zagnieżdżonej właściwości i nie tylko sprawdzić jej istnienie, oto prosta funkcja jednowierszowa:

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

Powyższa funkcja pozwala uzyskać wartość zagnieżdżonych właściwości, w przeciwnym razie zwróci undefined.

AKTUALIZACJA 17.10.2019:

Opcjonalnie propozycja łańcuchowym osiągnęła etap 3 na proces komisji ECMAScript , pozwoli to na bezpieczne dostępu właściwości głęboko zagnieżdżonych, za pomocą tokena ?., nowa opcja operator łańcuchowym :

const value = obj?.level1?.level2?.level3 

Jeśli którykolwiek z dostępnych poziomów jest nulllub undefinedwyrażenie zostanie rozwiązane undefinedsamodzielnie.

Ta propozycja pozwala również bezpiecznie obsługiwać wywołania metod:

obj?.level1?.method();

Powyższe wyrażenie wygeneruje undefinedif obj, obj.level1lub obj.level1.methodare nullor undefined, w przeciwnym razie wywoła funkcję.

Możesz zacząć grać z tą funkcją w Babel za pomocą opcjonalnej wtyczki łańcuchowej .

Od wersji Babel 7.8.0 ES2020 jest domyślnie obsługiwany

Sprawdź ten przykład na Babel REPL.

🎉🎉 AKTUALIZACJA: grudzień 2019 🎉🎉

Opcjonalna propozycja łączenia ostatecznie osiągnęła etap 4 na posiedzeniu komitetu TC39 w grudniu 2019 r. Oznacza to, że ta funkcja będzie częścią standardu ECMAScript 2020 .


4
argumentstak naprawdę nie jest tablicą. Array.prototype.slice.call(arguments)konwertuje go na formalną tablicę. Dowiedz się
odejdź

23
byłoby to o wiele bardziej wydajne, aby var obj = arguments[0];zacząć i zacząć od var i = 1zamiast kopiować argumentsobiekt
Claudiu

2
Złożyłem wersję z try / catch dla oszczędności, i nic dziwnego - wydajność jest okropna (z wyjątkiem Safari z jakiegoś powodu). Poniżej znajduje się kilka odpowiedzi, które są dość wydajne, wraz z modyfikacją Claudiu, która jest również znacznie bardziej wydajna niż wybrana odpowiedź. Zobacz jsperf tutaj jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

3
W ES6 argsdeklaracja zmiennej może zostać usunięta i ...args może być wykorzystana jako drugi argument checkNestedmetody. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon

7
To jest bardzo nie do utrzymania. Jeśli jakiekolwiek klucze właściwości zmienią się (zmienią), wszyscy deweloperzy w projekcie musieliby „przeszukać ciąg” całej bazy kodu. To nie jest tak naprawdę rozwiązanie problemu, ponieważ wprowadza znacznie większy problem
Drenai

357

Oto wzór, który wybrałem od Olivera Steele :

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

W rzeczywistości cały artykuł jest dyskusją na temat tego, jak to zrobić w javascript. Postanawia używać powyższej składni (co nie jest trudne do odczytania, gdy się przyzwyczaisz) jako idiomu.


8
@wared Myślę, że jest to interesujące przede wszystkim ze względu na zwięzłość. Szczegółowy opis cech wydajności znajduje się w powiązanym poście. Tak, zawsze wykonuje wszystkie testy, ale unika tworzenia zmiennych temp. Możesz też użyć aliasu {} do var, jeśli chcesz uniknąć narzutu związanego z tworzeniem nowego pustego obiektu za każdym razem. W 99% przypadków nie spodziewałbym się, że prędkość będzie miała znaczenie, a w przypadkach, w których tak się dzieje, profilowanie nie zastąpi.
Gabe Moothart,

9
@MuhammadUmer Nie, chodzi o (test || {})to, że jeśli test jest niezdefiniowany, to robisz ({}.level1 || {}). Oczywiście {}.level1jest niezdefiniowany, więc oznacza to, że robisz {}.level2i tak dalej.
Joshua Taylor

3
@JoshuaTaylor: Myślę, że ma na myśli, że jeśli testnie zostanie zadeklarowany, wystąpi błąd ReferenceError , ale to nie jest problem, ponieważ jeśli nie zostanie zadeklarowany, będzie błąd do naprawienia, więc błąd jest dobrą rzeczą.

34
powiedziałeś „co nie jest takie trudne do odczytania raz Ci przyzwyczaić się do niego” . Cóż, to znaki, które już wiesz , że to jest bałagan . Dlaczego więc proponujesz to rozwiązanie? Jest podatny na literówki i absolutnie nic nie poprawia czytelności. Tylko spójrz! Jeśli muszę napisać brzydką linię, powinna ona być czytelna ; więc pozostanę przy sobieif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky

8
Chyba, że ​​coś mi umknie, to nie zadziała dla boolowskich właściwości końcowych, które mogą być fałszywe ... niestety. W przeciwnym razie uwielbiam ten idiom.
T3db0t,

261

Aktualizacja

Wygląda na to, że lodash został dodany _.get do wszystkich potrzeb związanych z zagnieżdżaniem nieruchomości.

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

https://lodash.com/docs#get


Poprzednia odpowiedź

Użytkownicy lodash mogą korzystać z lodash.contrib, który ma kilka metod łagodzących ten problem .

getPath

Podpis: _.getPath(obj:Object, ks:String|Array)

Pobiera wartość na dowolnej głębokości w zagnieżdżonym obiekcie na podstawie ścieżki opisanej przez podane klucze. Klucze mogą być podawane jako tablica lub jako ciąg rozdzielany kropkami. Zwraca, undefinedjeśli ścieżka nie może zostać osiągnięta.

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 naprawdę potrzebuje metody _.isPathDefined (obj, pathString).
Matthew Payne

@MatthewPayne Być może byłoby miło, ale tak naprawdę nie jest konieczne. Możesz to zrobić naprawdę łatwofunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no

11
Lodash ma tę samą funkcjonalność:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom

jeszcze lepiej byłoby _. wynik
Shishir Arora

Jeśli chcesz ustalić wiele różnych ścieżek, rozważ: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison

210

Przeprowadziłem testy wydajności (dziękuję cdMinix za dodanie lodash) dla niektórych sugestii zaproponowanych do tego pytania z wynikami wymienionymi poniżej.

Zastrzeżenie nr 1 Przekształcanie ciągów w odwołania jest niepotrzebnym metaprogramowaniem i prawdopodobnie najlepiej go unikać. Nie trać na początku swoich referencji. Przeczytaj więcej z tej odpowiedzi na podobne pytanie .

Zastrzeżenie nr 2 Mówimy tutaj o milionach operacji na milisekundę. Jest bardzo mało prawdopodobne, aby którykolwiek z nich zrobiłby dużą różnicę w większości przypadków użycia. Wybierz, który z nich jest najbardziej sensowny, znając ograniczenia każdego z nich. Dla mnie wybrałbym coś reducez wygody.

Object Wrap (autor: Oliver Steele) - 34% - najszybciej

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

Oryginalne rozwiązanie (sugerowane w pytaniu) - 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%

najgłębsze - 86%

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

smutni klauni - 100% - najwolniej

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
należy zauważyć, że im więcej% testu ma - tym wolniej
lawina 1

2
co z lodash _.get()? jak skuteczny jest w porównaniu do tych odpowiedzi?
beniutek

1
Każda z nich jest wolniejsza lub szybsza niż inne, w zależności od sytuacji. Jeśli wszystkie klucze zostaną znalezione, wówczas „Zawijanie obiektu” może być najszybsze, ale jeśli jeden z kluczy nie zostanie znaleziony, wówczas „Rozwiązanie natywne / Rozwiązanie oryginalne” może być szybsze.
evilReiko

1
@evilReiko Dowolna metoda będzie wolniejsza, jeśli nie zostaną znalezione żadne klucze, ale proporcjonalnie do siebie jest nadal prawie taka sama. Masz jednak rację - jest to bardziej ćwiczenie intelektualne niż cokolwiek innego. Mówimy tutaj o milionie iteracji na milisekundę. Nie widzę przypadku użycia, w którym miałoby to znaczącą różnicę. Ja osobiście wybrałbym reducelub try/catchnie dla wygody.
unitario

Jaka jest wydajność w porównaniu dotry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

Można odczytać właściwość obiektu na każdej głębokości, jeśli uchwyt nazwę jak struna: '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');

Zwraca, undefinedjeśli którykolwiek z segmentów jest undefined.


3
Warto zauważyć, że ta metoda jest bardzo wydajna, przynajmniej w Chrome, w niektórych przypadkach przewyższa zmodyfikowaną wersję wybranej odpowiedzi @Claudiu. Zobacz test wydajności tutaj: 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; })
);

Jeśli kodujesz w środowisku ES6 (lub używasz 6to5 ), możesz skorzystać ze składni funkcji strzałek :

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

Jeśli chodzi o wydajność, nie ma ograniczenia wydajności za korzystanie try..catch bloku, jeśli właściwość jest ustawiona. Jeśli właściwość jest rozbrojona, ma to wpływ na wydajność.

Rozważ użycie _.has:

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

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

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

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

2
Myślę, że to try-catchpodejście jest najlepszą odpowiedzią. Istnieje filozoficzna różnica między zapytaniem o obiekt dla jego typu, a założeniem, że interfejs API istnieje, i odpowiednio się nie powiedzie, jeśli nie będzie. To drugie jest bardziej odpowiednie w luźno pisanych językach. Zobacz stackoverflow.com/a/408305/2419669 . try-catchPodejście jest również znacznie jaśniejsze niż if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory

24

Co powiesz na

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

}

15
Nie sądzę, aby try / catch to dobry sposób na sprawdzenie istnienia obiektu: try / catch ma na celu obsługę wyjątków, a nie normalnych warunków, takich jak test tutaj. Myślę, że (typeof foo == "undefined") na każdym kroku jest lepszy - i ogólnie rzecz biorąc, prawdopodobnie konieczne jest pewne refaktoryzowanie, jeśli pracujesz z tak głęboko zagnieżdżonymi właściwościami. Również try / catch spowoduje przerwanie w Firebug (i w każdej przeglądarce, w której włączony jest błąd break-on-error), jeśli zostanie zgłoszony wyjątek.
Sam Dutton,

Głosuję nad tym, ponieważ przeglądarka sprawdzi istnienie dwa razy, jeśli użyjesz innych rozwiązań. Powiedzmy, że chcesz zadzwonić „acb = 2”. Przeglądarka musi sprawdzić istnienie przed modyfikacją wartości (w przeciwnym razie byłby to błąd pamięci wyłapany przez system operacyjny).

4
Pozostaje pytanie: czy przeglądarki szybciej skonfigurują próbę złapania lub połączenia hasOwnProperty()n razy?

14
Dlaczego znów jest tak źle? Dla mnie to wygląda najczystiej.
Austin Pray

Powiedziałbym: jeśli spodziewasz się, że właściwość istnieje, to dobrze jest zawinąć ją w blok try. Jeśli to nie istnieje, oznacza to błąd. Ale jeśli jesteś po prostu leniwy i umieścisz regularny kod w bloku catch na wypadek, gdyby właściwość nie istniała, spróbuj użyć / catch do niewłaściwego użycia. Tutaj wymagany jest if / else lub coś podobnego.
robsch

18

Odpowiedź ES6, dokładnie przetestowana :)

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

→ patrz Codepen z pełnym pokryciem testowym


Sprawiłem, że twoje testy nie powiodły się, ustawiając wartość płaskiego rekwizytu na 0. Musisz dbać o przymus typu.
Germain

@germain Czy to działa dla Ciebie? (Wyraźnie porównuję ===różne falsys i dodałem test. Jeśli masz lepszy pomysł, daj mi znać).
Frank Nocke,

Sprawiłem, że twoje testy nie powiodły się, ustawiając wartość płaskiego rekwizytu na false. A potem możesz chcieć mieć wartość w swoim obiekcie ustawioną na undefined(wiem, że to dziwne, ale JS). Zrobiłem dodatnią fałszywą wartość ustawioną na '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

Czy potrafisz rozwidlić mój codepen (w prawym górnym rogu, łatwo), poprawić i dodać testy oraz wysłać mi swój adres URL? Dzięki =)
Frank Nocke

Uciekam do (ogromnej) biblioteki innej firmy ... możliwe, ale nie moje preferencje.
Frank Nocke,

17

Możesz również użyć opcjonalnej kombinacji łańcuchowej tc39 wraz z babel 7 - tc39-propozycja-opcjonalna łączenie łańcuchów

Kod wyglądałby tak:

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

Zauważ, że ta składnia prawie na pewno się zmieni, ponieważ niektórzy członkowie TC39 mają zastrzeżenia.
jhpratt GOFUNDME RELICENSING

Prawdopodobnie, ale to będzie dostępne w jakiejś formie na czas, a to jedyna rzecz, która się liczy. Jest to jedna z funkcji, za którymi tęsknię najbardziej w JS.
Goran.it

11

Wypróbowałem podejście rekurencyjne:

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

W ! keys.length ||wyrzuca z rekursji więc nie uruchomić funkcję bez pozostawionych do testu kluczy. Testy:

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

Używam go do drukowania przyjaznego widoku html wiązki obiektów o nieznanych kluczach / wartościach, np .:

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';

9

Myślę, że poniższy skrypt daje bardziej czytelną reprezentację.

zadeklaruj funkcję:

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

użyj tego w następujący sposób:

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

}

Nazywam to „techniką smutnego klauna”, ponieważ używa znaku o (


EDYTOWAĆ:

tutaj jest wersja dla TypeScript

daje kontrolę typu w czasie kompilacji (a także inteligencję, jeśli używasz narzędzia takiego jak Visual Studio)

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

użycie jest takie samo:

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

ale tym razem intellisense działa!

ponadto możesz ustawić wartość domyślną:

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

1
°0o <°(())))><
Daniel W.,

1
Podoba mi się ten, ponieważ jest uczciwy i rzuca w twarz „niezdefiniowany”, gdy nie znasz swojego Objecttypu. +1.

1
Tak długo, jak trzymasz to wyrażenie w parenach, możesz także nazwać je techniką szczęśliwego klauna (o
Sventies

Dzięki Sventies. Kocham twój komentarz. Kąt patrzenia jest całkiem ładny - takie warunki są najczęściej używane w „ifs” i zawsze otoczone zewnętrznymi nawiasami. Tak, tak, to w większości szczęśliwy klaun :)))
VeganHunter

Naprawdę musisz się zakochać w nawiasie, aby wybrać ten ...
Bastien7

7

stworzyć globalny function i wykorzystaj w całym projekcie

Spróbuj tego

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

jeśli warunek

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

Działa świetnie. Prosty i wydajny
gbland777

6

Jednym prostym sposobem jest:

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

try/catchPołowy przypadków, w przypadku każdego z wyższego poziomu obiektów, takich jak badania, test.level1, test.level1.level2 nie są określone.


6

Nie widziałem żadnego przykładu osoby używającej proxy

Więc wymyśliłem własne. Wspaniałą rzeczą jest to, że nie trzeba interpolować ciągów. Możesz faktycznie zwrócić funkcję obiektu zdolną do tworzenia łańcuchów i robić z nią magiczne rzeczy. Możesz nawet wywoływać funkcje i uzyskiwać indeksy tablic w celu sprawdzenia głębokich obiektów

Powyższy kod działa dobrze dla rzeczy synchronicznych. Ale jak przetestowałbyś coś tak asynchronicznego, jak to wywołanie ajax? Jak to testujesz? co jeśli odpowiedź nie jest json, gdy zwraca błąd 500 HTTP?

window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

upewnij się, że możesz użyć asynchronizacji / oczekiwania na pozbycie się niektórych wywołań zwrotnych. A gdybyś mógł to zrobić jeszcze bardziej magicznie? coś, co wygląda tak:

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

Prawdopodobnie zastanawiasz się, gdzie są wszystkie obietnice i .thenłańcuchy ... to może blokować wszystko, co wiesz ... ale używając tej samej techniki proxy z obietnicą możesz faktycznie przetestować głęboko zagnieżdżoną złożoną ścieżkę pod kątem jej istnienia bez pisania żadnej funkcji


Jeśli ktoś jest zainteresowany, opublikowałem wersję asynchroniczną na npm
Endless

5

Na podstawie tej odpowiedzi wymyśliłem tę ogólną funkcję, ES2015która rozwiązałaby problem

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
Oto moje ostatnie podejściefunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington,

5

Stworzyłem małą funkcję, aby bezpiecznie uzyskać właściwości zagnieżdżonego obiektu.

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

Lub prostsza, ale nieco nieczytelna wersja:

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

Lub nawet krócej, ale bez rezerwy na fladze falsy:

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

Mam test z:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

A oto kilka testów:

// 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'));

Aby zobaczyć cały kod z dokumentacją i próbami, które wypróbowałem, możesz sprawdzić moją listę github: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

Krótsza wersja doskonałej odpowiedzi @ CMS na ES5:

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

Z podobnym testem:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

jedynym problemem jest to, że istnieje wiele poziomów niezdefiniowanych kluczy, a następnie pojawia się błąd typu, np.checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS

1
Bardziej odpowiednią metodą jest każda , której wartość można zwrócić bezpośrednio.
RobG

Może zmień success = false;na return false. Powinieneś wyskoczyć, gdy wiesz, że się psuje, nic głębszego nie może istnieć, gdy jest zerowe lub niezdefiniowane. Pozwoliłoby to uniknąć błędów w głęboko zagnieżdżonych elementach, ponieważ oczywiście one również nie istnieją.
Wade

4

Szukałem wartości, która ma zostać zwrócona, jeśli właściwość istnieje, więc zmodyfikowałem odpowiedź przez CMS powyżej. Oto, co wymyśliłem:

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

Odpowiedź podana przez CMS działa dobrze z następującą modyfikacją również dla kontroli zerowych

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

Na podstawie tej odpowiedzi opracowano następujące opcje . To samo drzewo dla obu:

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

Zatrzymaj wyszukiwanie, gdy nie jest zdefiniowane

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

Zapewniaj każdy poziom jeden po drugim

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined

3

Wiem, że to pytanie jest stare, ale chciałem zaoferować rozszerzenie, dodając je do wszystkich obiektów. Wiem, że ludzie nie lubią używać prototypu Object do rozszerzonej funkcjonalności obiektu, ale nie znajduję nic łatwiejszego niż robienie tego. Ponadto jest to teraz dozwolone w przypadku metody 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;
}});

Teraz, aby przetestować dowolną właściwość w dowolnym obiekcie, możesz po prostu zrobić:

if( obj.has("some.deep.nested.object.somewhere") )

Oto jsfiddle do przetestowania go, aw szczególności zawiera on jQuery, który psuje się, jeśli zmodyfikujesz Object.prototype bezpośrednio z powodu wyliczenia właściwości. Powinno to działać poprawnie z bibliotekami stron trzecich.


3

Myślę, że to niewielka poprawa (zmienia się w 1-liniowy):

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

Działa to, ponieważ operator && zwraca końcowy operand, który ocenił (i powoduje zwarcie).


Dosłownie skopiowałeś to, co powiedzieli, że zwykle robią, i chcesz uniknąć ...
Sean Kendle

3

Działa to ze wszystkimi obiektami i tablicami :)

dawny:

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

to jest moja ulepszona wersja odpowiedzi Briana

Użyłem _has jako nazwy właściwości, ponieważ może powodować konflikt z istniejącą ma właściwość (np. Mapy)

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

Oto skrzypce


3

Oto moje zdanie na ten temat - większość z tych rozwiązań ignoruje przypadek zagnieżdżonej tablicy jak w:

    obj = {
        "l1":"something",
        "l2":[{k:0},{k:1}],
        "l3":{
            "subL":"hello"
        }
    }

Mogę chcieć sprawdzić obj.l2[0].k

Dzięki funkcji poniżej możesz to zrobić deeptest('l2[0].k',obj)

Funkcja zwróci true, jeśli obiekt istnieje, w przeciwnym razie 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

Teraz możemy także używać reducedo przechodzenia przez zagnieżdżone klucze:

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

Możesz to zrobić za pomocą funkcji rekurencyjnej. Działa to nawet wtedy, gdy nie znasz wszystkich nazw zagnieżdżonych kluczy Object.

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

jest tu funkcja na codecode (safeRead), która zrobi to w bezpieczny sposób ... tj

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

jeśli jakakolwiek właściwość jest pusta lub niezdefiniowana, zwracany jest pusty ciąg


Podoba mi się ta metoda z szablonami, ponieważ zwraca pusty ciąg, jeśli nie jest ustawiony
Lounge9

2

W oparciu o poprzedni komentarz , oto kolejna wersja, w której główny obiekt nie mógł zostać zdefiniowany:

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

2

Napisałem własną funkcję, która podąża pożądaną ścieżkę i ma dobrą i złą funkcję wywołania zwrotnego.

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

Stosowanie:

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

Uważam, że mogę uczciwie
podziękować

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.