Teste a existência de chave de objeto JavaScript aninhado

691

Se eu tiver uma referência a um objeto:

var test = {};

que potencialmente (mas não imediatamente) terão objetos aninhados, algo como:

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

Qual é a melhor maneira de verificar a existência de propriedade em objetos profundamente aninhados?

alert(test.level1);produz undefined, mas alert(test.level1.level2.level3);falha.

Atualmente, estou fazendo algo parecido com isto:

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

mas eu queria saber se existe uma maneira melhor.

user113716
fonte
1
você pode querer verificar uma pergunta tangencialmente relacionado que foi perguntado recentemente stackoverflow.com/questions/2525943/...
Anurag
Veja também stackoverflow.com/questions/10918488/…
James McMahon
Algumas proposições lá: stackoverflow.com/a/18381564/1636522
leaf
Sua abordagem atual tem um potencial problema se a propriedade level3 é uma falsa, nesse caso, mesmo que a exist propriedade irá retur nfalse ter um olhar para este exemplo por favor jsfiddle.net/maz9bLjx
GibboK
10
simplesmente você pode usar try catch também
Raghavendra

Respostas:

487

Você deve fazer isso passo a passo se não quiser um, TypeErrorporque se um dos membros for nullor undefinede tentar acessar um membro, uma exceção será lançada.

Você pode simplesmente fazer catcha exceção ou criar uma função para testar a existência de vários níveis, algo como isto:

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

ATUALIZAÇÃO ES6:

Aqui está uma versão mais curta da função original, usando os recursos e a recursão do ES6 (também está no formulário de chamada de cauda apropriado ):

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

No entanto, se você deseja obter o valor de uma propriedade aninhada e não apenas verificar sua existência, aqui está uma função simples de uma linha:

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

A função acima permite que você obtenha o valor das propriedades aninhadas, caso contrário, retornará undefined.

ATUALIZAÇÃO 2019-10-17:

A proposta de encadeamento opcional chegou ao Estágio 3 no processo do comitê ECMAScript , permitindo acessar com segurança propriedades profundamente aninhadas, usando o token ?., o novo operador de encadeamento opcional :

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

Se algum dos níveis acessados ​​for nullou undefineda expressão será resolvida undefinedpor si mesma.

A proposta também permite lidar com chamadas de método com segurança:

obj?.level1?.method();

A expressão acima produzirá undefinedse obj, obj.level1ou obj.level1.methodsão nullou undefined, caso contrário irá chamar a função.

Você pode começar a jogar com esse recurso com Babel usando o plug-in de encadeamento opcional .

Desde o Babel 7.8.0 , o ES2020 é suportado por padrão

Veja este exemplo no Babel REPL.

🎉🎉UPDATE: dezembro de 2019 🎉🎉

A proposta de encadeamento opcional finalmente chegou ao Estágio 4 na reunião de dezembro de 2019 do comitê do TC39. Isso significa que esse recurso fará parte do Padrão ECMAScript 2020 .

CMS
fonte
4
argumentsnão é realmente uma matriz. Array.prototype.slice.call(arguments)converte-o em uma matriz formal. Aprender
deefour
23
this'd ser muito mais eficiente para fazer var obj = arguments[0];e começar a partir var i = 1em vez de copiar o argumentsobjeto
Claudiu
2
Eu montei uma versão com try / catch por uma questão de austeridade, e nenhuma surpresa - o desempenho é péssimo (exceto no Safari por algum motivo). Existem algumas respostas abaixo que são bastante eficazes, juntamente com a modificação de Claudiu, que também é significativamente mais eficiente do que a resposta selecionada. Veja jsperf aqui jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica
3
No ES6, a argsdeclaração da variável pode ser removida e ...args pode ser usada como o segundo argumento para o checkNestedmétodo. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon
6
Isso é muito insustentável. Se alguma chave de propriedade mudar (elas mudarão), todos os desenvolvedores do projeto precisariam 'pesquisar por string' por toda a base de código. Isto não é realmente uma solução para o problema, uma vez que introduz um problema muito maior
Drenai
356

Aqui está um padrão que peguei de Oliver Steele :

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

De fato, todo o artigo é uma discussão de como você pode fazer isso em javascript. Ele decide usar a sintaxe acima (que não é tão difícil de ler quando você se acostuma) como um idioma.

Gabe Moothart
fonte
8
@ wared Eu acho que é interessante principalmente pela forma como é conciso. Há uma discussão detalhada das características de desempenho na postagem vinculada. Sim, ele sempre realiza todos os testes, mas evita a criação de vars temporários, e você pode usar o alias {} para um var se quiser evitar a sobrecarga de criar um novo objeto vazio a cada vez. Em 99% dos casos, eu não esperaria que a velocidade importasse, e nos casos em que ocorre, não há substituto para o perfil.
Gabe Moothart
9
@MuhammadUmer Não, o objetivo (test || {})é que, se o teste for indefinido, você está fazendo ({}.level1 || {}). Obviamente, {}.level1é indefinido, o que significa que você está fazendo {}.level2e assim por diante.
27415 Joshua Taylor #
3
@ JoshuaTaylor: Acho que ele quer dizer que se testnão for declarado, haverá um ReferenceError , mas isso não é um problema, porque se não for declarado, há um bug a ser corrigido, portanto o erro é uma coisa boa.
33
você disse "o que não é tão difícil de ler quando você se acostuma " . Bem, estes são sinais que você já sabe que isso é uma bagunça . Então, por que sugerir esta solução? É propenso a erros de digitação e não dá absolutamente nada à legibilidade. Basta olhar! Se eu tiver que escrever uma linha feia, ela deve ser legível ; então eu vou ficar comif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky
8
A menos que esteja faltando alguma coisa, isso não funcionará para propriedades finais booleanas que podem ser falsas ... infelizmente. Caso contrário, eu amo esse idioma.
T3db0t
261

Atualizar

Parece que o lodash foi adicionado _.get para que todas as suas propriedades aninhadas obtenham necessidades.

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

https://lodash.com/docs#get


Resposta anterior

Os usuários do lodash podem aproveitar o lodash.contrib, que possui alguns métodos que atenuam esse problema .

getPath

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

Obtém o valor em qualquer profundidade em um objeto aninhado com base no caminho descrito pelas chaves fornecidas. As chaves podem ser fornecidas como uma matriz ou como uma sequência separada por pontos. Retorna undefinedse o caminho não puder ser alcançado.

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
Austin Pray
fonte
O Lodash realmente precisa de um método _.isPathDefined (obj, pathString).
Matthew Payne
@ MatthewPayne Seria bom, talvez, mas realmente não é necessário. Você poderia fazê-lo sozinho muito facilmentefunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no
11
O Lodash possui essa mesma funcionalidade:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom
ainda melhor seria _.result
Arora
Se você precisa para determinar vários caminhos diferentes considerar: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison
209

Fiz testes de desempenho (obrigado cdMinix por adicionar o lodash) em algumas das sugestões propostas para esta pergunta com os resultados listados abaixo.

Isenção de responsabilidade nº 1 Transformar strings em referências é meta-programação desnecessária e provavelmente é melhor evitar. Não perca o controle de suas referências para começar. Leia mais desta resposta para uma pergunta semelhante .

Isenção de responsabilidade nº 2 Estamos falando de milhões de operações por milissegundo aqui. É muito improvável que qualquer um deles faça muita diferença na maioria dos casos de uso. Escolha o que fizer mais sentido, conhecendo as limitações de cada um. Para mim, eu iria com algo como reducepor conveniência.

Quebra de Objetos (de Oliver Steele) - 34% - mais rápido

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

Solução original (sugerida em questão) - 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%

mais profundo - 86%

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

palhaços tristes - 100% - mais lento

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
fonte
16
deve notar-se que quanto mais um teste tem% - Quanto mais lenta é
avalanche1
2
e o lodash _.get()? qual é o desempenho em comparação com essas respostas?
beniutek 31/05
1
Cada método é mais lento ou mais rápido do que outros, dependendo da situação. Se todas as chaves forem encontradas, o "Quebra de Objeto" poderá ser o mais rápido, mas se uma das chaves não for encontrada, "Solução nativa / Solução original" poderá ser mais rápida.
precisa saber é o seguinte
1
@evilReiko Qualquer método será mais lento se nenhuma chave for encontrada, mas proporcionalmente uma à outra ainda é praticamente a mesma. No entanto, você está certo - este é mais um exercício intelectual do que qualquer outra coisa. Estamos falando de um milhão de iterações por milissegundo aqui. Não vejo nenhum caso de uso em que isso faça muita diferença. Pessoalmente, eu iria por reduceou try/catchpor conveniência.
unitario 30/08/19
Qual é o desempenho em comparação comtry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex
46

Você pode ler uma propriedade do objeto em qualquer profundidade, se você lida com o nome como uma string: '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');

Retorna undefinedse algum dos segmentos for undefined.

Kennebec
fonte
3
Vale ressaltar que esse método é muito eficiente, pelo menos no Chrome, em alguns casos, superando a versão modificada da resposta selecionada @Claudiu. Veja o teste de desempenho aqui: 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; })
);

Se você estiver codificando no ambiente ES6 (ou usando 6to5 ), poderá aproveitar a sintaxe da função de seta :

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

Em relação ao desempenho, não há penalidade de desempenho por usar o try..catchbloco se a propriedade estiver configurada. Há um impacto no desempenho se a propriedade estiver desmarcada.

Considere simplesmente usar _.has:

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

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

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

_.has(object, ['a', 'b', 'c']);
// → true
Gajus
fonte
2
Eu acho que a try-catchabordagem é a melhor resposta. Há uma diferença filosófica entre consultar um objeto para seu tipo e supor que a API exista e falhar de acordo, se não existir. O último é mais apropriado em idiomas de baixa digitação. Consulte stackoverflow.com/a/408305/2419669 . A try-catchabordagem também é muito mais clara do que if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
precisa saber é o seguinte
24

e quanto a

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

}
user187291
fonte
15
Eu não acho que try / catch é uma boa maneira de testar a existência de um objeto: try / catch é feito para lidar com exceções, não com condições normais, como o teste aqui. Eu acho que (typeof foo == "indefinido") em cada etapa é melhor - e, em geral, provavelmente há alguma refatoração necessária se você estiver trabalhando com propriedades profundamente aninhadas. Além disso, try / catch causará uma interrupção no Firebug (e em qualquer navegador em que o erro de interrupção esteja ativado) se uma exceção for lançada.
Sam Dutton
Eu voto sobre isso, porque o navegador verificará a existência duas vezes se você usar outras soluções. Digamos que você queira chamar 'acb = 2'. O navegador precisa verificar a existência antes de modificar o valor (caso contrário, seria um erro de memória detectado pelo sistema operacional).
4
A questão ainda permanece: qual é mais rápido para os navegadores configurarem uma tentativa de captura ou ligação hasOwnProperty()n vezes?
14
Por que isso é ruim de novo? Isso me parece mais limpo.
Austin Ore
Eu diria: Se você espera que a propriedade exista, não há problema em envolvê-la em um bloco try. Se não existir, é um erro. Mas se você for preguiçoso e colocar código regular no bloco catch, caso a propriedade não exista, try / catch será mal utilizado. Aqui é necessário um if / else ou algo semelhante.
robsch
18

Resposta ES6, exaustivamente testada :)

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

→ veja Codepen com cobertura completa de teste

Frank Nocke
fonte
Fiz seus testes falharem ao definir o valor do suporte plano como 0. Você deve se preocupar com a coerção de tipos.
Germain #
@germain faz este trabalho para você? (Eu comparo explicitamente ===para os diferentes falsys e adicionei o teste. Se você tiver uma idéia melhor, me avise).
Frank Nocke #
Fiz seus testes falharem novamente, definindo o valor do suporte plano como false. E então você pode querer ter um valor no seu objeto definido como undefined(eu sei que é estranho, mas é JS). Eu fiz um valor falso positivo definido como '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
Você pode bifurcar meu codepen (canto superior direito, fácil), corrigir e adicionar testes e me enviar a URL sua? Obrigado =) #
Frank Nocke #
Fugir para uma (enorme) biblioteca de terceiros ... possível, mas não a minha preferência.
precisa
17

Você também pode usar a proposta de encadeamento opcional do tc39 junto com o babel 7 - tc39-proposta-encadeamento opcional

O código ficaria assim:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);
Goran.it
fonte
Observe que essa sintaxe quase certamente mudará, pois alguns membros do TC39 têm objeções.
Jhpratt GOFUNDME RELICENSING
Provavelmente, mas isso estará disponível de alguma forma no tempo, e essa é a única coisa que importa .. É um dos recursos que mais sinto falta no JS.
Goran.it 24/09
11

Eu tentei uma abordagem recursiva:

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

Ele ! keys.length ||inicia a recursão para que não execute a função sem as teclas restantes para testar. Testes:

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

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

Estou usando-o para imprimir uma visualização html amigável de um monte de objetos com valores / chaves desconhecidos, por exemplo:

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

Eu acho que o script a seguir fornece uma representação mais legível.

declare uma função:

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

use-o assim:

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

}

Eu chamo isso de "técnica do palhaço triste" porque está usando o sinal o (


EDITAR:

aqui está uma versão para TypeScript

fornece verificações de tipo em tempo de compilação (assim como o intellisense, se você usar uma ferramenta como o 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 uso é o mesmo:

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

mas desta vez o intellisense funciona!

Além disso, você pode definir um valor padrão:

o(o(o(o(o(test).level1).level2).level3, "none")
VeganHunter
fonte
1
°0o <°(())))><
Daniel W.
1
Eu gosto deste, porque é honesto e joga um "indefinido" na sua cara quando você não conhece o seu Objecttipo. +1.
1
Contanto que você mantenha a declaração em parênteses, você também pode chamá-la de técnica de palhaço feliz (o
Sventies 22/01/19
Obrigado Sventies. Eu amo o seu comentário. É um ângulo bastante agradável de se olhar - essas condições são usadas principalmente em "ifs" e sempre cercadas por colchetes externos. Então, sim, é principalmente um palhaço feliz mesmo :))))
VeganHunter
Você realmente precisa estar apaixonada por parêntesis para ir para um presente ...
Bastien7
7

criar um global functione usar em todo o projeto

tente isso

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

se condição

if(isExist(()=>obj.test.foo)){
   ....
}
Kelvin Kantaria
fonte
Funciona bem. Simples e eficiente
gbland777 11/01
6

Uma maneira simples é esta:

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

Ele try/catchcaptura os casos em que qualquer um dos objetos de nível superior, como test, test.level1, test.level1.level2 não está definido.

jfriend00
fonte
6

Não vi nenhum exemplo de alguém usando Proxies

Então eu criei o meu. O melhor de tudo é que você não precisa interpolar strings. Você pode realmente retornar uma função de objeto capaz de cadeia e fazer algumas coisas mágicas com ela. Você pode até chamar funções e obter índices de matriz para verificar objetos profundos

O código acima funciona bem para coisas síncronas. Mas como você testaria algo assíncrono como esta chamada ajax? Como você testa isso? e se a resposta não for json quando retornar um erro de 500 http?

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

Certifique-se de que você pode usar async / waitit para se livrar de alguns retornos de chamada. Mas e se você pudesse fazê-lo ainda mais magicamente? algo parecido com isto:

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

Você provavelmente se pergunta onde estão todas as promessas e .thencadeias ... isso pode estar bloqueando tudo o que você sabe ... mas, usando a mesma técnica Proxy com promessa, você pode realmente testar um caminho complexo profundamente aninhado para sua existência sem nunca escrever uma única função

Sem fim
fonte
Se alguém estiver interessado, publiquei a versão assíncrona no npm
Endless
5

Com base nesta resposta , criei esta função genérica usando a ES2015qual resolveria o problema

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 );
}
Alex Moldovan
fonte
1
Aqui é a minha aproximação finalfunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington
5

Eu criei uma pequena função para obter propriedades de objetos aninhados com segurança.

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

Ou uma versão mais simples, mas um pouco ilegível:

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

Ou ainda mais curto, mas sem fallback na bandeira do falso:

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

Eu tenho teste com:

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

E aqui estão alguns testes:

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

Para ver todo o código com a documentação e os testes que eu tentei, você pode verificar a minha lista do github: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

V. Sambor
fonte
4

Uma versão ES5 mais curta da excelente resposta do @ 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;
}

Com um teste semelhante:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false
mikemaccana
fonte
o único problema com isso é que, se houver vários níveis de chaves indefinidas, você obterá um TypeError, por exemplo:checkObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS
1
Um método mais adequado é todo , cujo valor pode ser retornado diretamente.
RobG
Talvez mude success = false;para return false. Você deve sair quando souber que quebra, nada mais profundo pode existir quando for nulo ou indefinido. Isso impediria os erros nos itens aninhados mais profundos, pois eles obviamente também não existem.
Wade
4

Eu acho que isso é uma ligeira melhora (se torna um liner):

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

Isso funciona porque o operador && retorna o operando final avaliado (e causa um curto-circuito).

Julius Musseau
fonte
4

Eu estava procurando o valor a ser retornado se a propriedade existir, então modifiquei a resposta do CMS acima. Aqui está o que eu vim com:

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

Noah Stahl
fonte
3

A resposta dada pelo CMS funciona bem com a seguinte modificação para verificações nulas também

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;
    }
Anand Sunderraman
fonte
3

As opções a seguir foram elaboradas a partir desta resposta . Mesma árvore para ambos:

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

Parar de pesquisar quando indefinido

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

Garanta que cada nível, um por um

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

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

Eu sei que essa pergunta é antiga, mas eu queria oferecer uma extensão adicionando isso a todos os objetos. Eu sei que as pessoas tendem a desaprovar o uso do protótipo de objeto para a funcionalidade estendida de objetos, mas não acho nada mais fácil do que fazer isso. Além disso, agora é permitido com o método 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;
}});

Agora, para testar qualquer propriedade em qualquer objeto, você pode simplesmente fazer:

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

Aqui está um jsfiddle para testá-lo e, em particular, inclui alguns jQuery que são quebrados se você modificar o Object.prototype diretamente devido à propriedade tornar-se enumerável. Isso deve funcionar bem com bibliotecas de terceiros.

Brian Sidebotham
fonte
3

Isso funciona com todos os objetos e matrizes :)

ex:

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

esta é minha versão aprimorada da resposta de Brian

Eu usei _has como o nome da propriedade, pois pode entrar em conflito com a propriedade has existente (ex: mapas)

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

Aqui está o violino

adutu
fonte
3

Aqui está minha opinião - a maioria dessas soluções ignora o caso de uma matriz aninhada, como em:

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

Eu posso querer procurar obj.l2[0].k

Com a função abaixo, você pode fazer deeptest('l2[0].k',obj)

A função retornará true se o objeto existir, false caso contrário

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

Mike D
fonte
3

Agora também podemos usar o reduceloop através de chaves aninhadas:

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

Egor Stambakio
fonte
3

Você pode fazer isso usando a função recursiva. Isso funcionará mesmo se você não souber o nome de todas as chaves de objetos aninhadas.

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;
}
Ankit Arya
fonte
2

existe uma função aqui no codecode (safeRead) que fará isso de maneira segura ...

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

se alguma propriedade for nula ou indefinida, uma sequência vazia será retornada

Ben
fonte
Eu meio que gosto este método com templates porque ele retorna uma string vazia se não for definido
Lounge9
2

Com base em um comentário anterior , aqui está outra versão em que o objeto principal também não pôde ser definido:

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

Eu escrevi minha própria função que segue o caminho desejado e tem uma função de retorno de chamada boa e ruim.

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

Uso:

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
Stephane LaFlèche
fonte
I embora justo dar-lhe crédito para a inspiração para adaptar seu código para a minha resposta
davewoodhall
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
fonte