Prueba de existencia de clave de objeto JavaScript anidada

691

Si tengo una referencia a un objeto:

var test = {};

que potencialmente (pero no inmediatamente) tendrá objetos anidados, algo como:

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

¿Cuál es la mejor manera de verificar la existencia de propiedades en objetos profundamente anidados?

alert(test.level1);cede undefined, pero alert(test.level1.level2.level3);falla.

Actualmente estoy haciendo algo como esto:

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

pero me preguntaba si hay una mejor manera.

usuario113716
fuente
1
es posible que desee verificar una pregunta relacionada tangencialmente que se le hizo recientemente stackoverflow.com/questions/2525943/…
Anurag
Ver también stackoverflow.com/questions/10918488/…
James McMahon
Un par de propuestas allí: stackoverflow.com/a/18381564/1636522
hoja del
Su enfoque actual tiene un problema potencial si la propiedad de nivel 3 es falsa, en ese caso, incluso si la propiedad existe volverá falsamente eche un vistazo a este ejemplo, por favor jsfiddle.net/maz9bLjx
GibboK
10
simplemente puedes usar try catch también
Raghavendra

Respuestas:

487

Tienes que hacerlo paso a paso si no quieres un TypeErrorporque si uno de los miembros es nullo undefined, e intentas acceder a un miembro, se lanzará una excepción.

Puede simplemente catchla excepción, o hacer una función para probar la existencia de múltiples niveles, algo como esto:

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

ACTUALIZACIÓN ES6:

Aquí hay una versión más corta de la función original, que usa las funciones y la recursión de ES6 (también está en forma de cola ):

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

Sin embargo, si desea obtener el valor de una propiedad anidada y no solo verificar su existencia, aquí hay una función simple de una línea:

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

La función anterior le permite obtener el valor de las propiedades anidadas, de lo contrario volverá undefined.

ACTUALIZACIÓN 2019-10-17:

La propuesta de encadenamiento opcional alcanzó la Etapa 3 en el proceso del comité ECMAScript , esto le permitirá acceder de manera segura a propiedades profundamente anidadas, utilizando el token ?., el nuevo operador de encadenamiento opcional :

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

Si alguno de los niveles accedidos es nullo undefinedla expresión se resolverá undefinedpor sí misma.

La propuesta también le permite manejar llamadas a métodos de manera segura:

obj?.level1?.method();

La expresión anterior producirá undefinedif obj, obj.level1or obj.level1.methodare nullo undefined, de lo contrario, llamará a la función.

Puedes comenzar a jugar con esta función con Babel usando el complemento de encadenamiento opcional .

Desde Babel 7.8.0 , ES2020 es compatible por defecto

Verifique este ejemplo en el REPL de Babel.

🎉🎉 ACTUALIZACIÓN: diciembre de 2019 🎉🎉

La propuesta de encadenamiento opcional finalmente alcanzó la Etapa 4 en la reunión de diciembre de 2019 del comité TC39. Esto significa que esta característica será parte del Estándar ECMAScript 2020 .

CMS
fuente
44
argumentsEn realidad no es una matriz. Array.prototype.slice.call(arguments)lo convierte en una matriz formal. Aprender
deefour
23
esto sería mucho más eficiente de hacer var obj = arguments[0];y comenzar desde en var i = 1lugar de copiar el argumentsobjeto
Claudiu
2
Creé una versión con try / catch por austeridad, y no es de extrañar: el rendimiento es horrible (excepto en Safari por alguna razón). Hay algunas respuestas a continuación que son bastante efectivas, junto con la modificación de Claudiu, que también es significativamente más eficaz que la respuesta seleccionada. Ver jsperf aquí jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica
3
En ES6, la argsdeclaración de variable se puede eliminar y ...args se puede utilizar como el segundo argumento para el checkNestedmétodo. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon
66
Esto es muy difícil de mantener. Si alguna clave de propiedad cambia (lo harán), todos los desarrolladores del proyecto tendrían que 'buscar en cadena' en toda la base de código. Esto no es realmente una solución al problema, ya que presenta un problema mucho mayor
Drenai,
356

Aquí hay un patrón que tomé de Oliver Steele :

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

De hecho, todo el artículo es una discusión sobre cómo puede hacer esto en javascript. Él decide usar la sintaxis anterior (que no es tan difícil de leer una vez que te acostumbras) como idioma.

Gabe Moothart
fuente
8
@wared Creo que es interesante sobre todo por lo conciso que es. Hay una discusión detallada de las características de rendimiento en la publicación vinculada. Sí, siempre realiza todas las pruebas, pero evita la creación de variables temporales, y puede crear un alias {} para una variable si desea evitar la sobrecarga de crear un nuevo objeto vacío cada vez. En el 99% de los casos, no esperaría que la velocidad importara, y en los casos en que lo hace, no hay sustituto para la creación de perfiles.
Gabe Moothart el
99
@ MuhammadUmer No, el punto (test || {})es que si la prueba no está definida, entonces lo estás haciendo ({}.level1 || {}). Por supuesto, {}.level1no está definido, lo que significa que lo estás haciendo {}.level2, y así sucesivamente.
Joshua Taylor
3
@JoshuaTaylor: Creo que quiere decir que si testno se declara, habrá un Error de referencia , pero eso no es un problema, porque si no se declara, hay un error que corregir, por lo que el error es algo bueno.
34
dijiste "que no es tan difícil de leer una vez que te acostumbras " . Bueno, estos son signos que ya sabes, esto es un desastre . Entonces, ¿por qué sugerir esta solución? Es propenso a los errores tipográficos y no da absolutamente nada a la legibilidad. ¡Solo míralo! Si tengo que escribir una línea fea, debería ser legible ; así que me voy a quedar conif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky
8
A menos que me falte algo, esto no funcionará para las propiedades finales booleanas que podrían ser falsas ... lamentablemente. De lo contrario, amo este idioma.
T3db0t
261

Actualizar

Parece que lodash ha agregado _.get todas sus necesidades de obtención de propiedades anidadas.

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

https://lodash.com/docs#get


Respuesta anterior

Los usuarios de lodash pueden disfrutar de lodash.contrib que tiene un par de métodos que mitigan este problema .

getPath

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

Obtiene el valor a cualquier profundidad en un objeto anidado en función de la ruta descrita por las claves proporcionadas. Las claves se pueden dar como una matriz o como una cadena separada por puntos. Devuelve undefinedsi no se puede llegar a la ruta.

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 reza
fuente
Lodash realmente necesita un método _.isPathDefined (obj, pathString).
Matthew Payne
@MatthewPayne Sería bueno quizás, pero realmente no es necesario. Podrías hacerlo tú mismo muy fácilmentefunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no
11
Lodash tiene esta misma funcionalidad en sí:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom
aún mejor sería _.resultado
Shishir Arora
Si necesita determinar varias rutas diferentes, considere: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison
209

He realizado pruebas de rendimiento (gracias cdMinix por agregar lodash) en algunas de las sugerencias propuestas para esta pregunta con los resultados que se enumeran a continuación.

Descargo de responsabilidad # 1 Convertir cadenas en referencias es una metaprogramación innecesaria y probablemente sea mejor evitarla. Para empezar, no pierdas de vista tus referencias. Lea más de esta respuesta a una pregunta similar .

Descargo de responsabilidad # 2 Estamos hablando de millones de operaciones por milisegundo aquí. Es muy poco probable que alguno de estos haga mucha diferencia en la mayoría de los casos de uso. Elija lo que tenga más sentido conociendo las limitaciones de cada uno. Para mí iría con algo como reducepor conveniencia.

Envoltura de objetos (por Oliver Steele) - 34% - más rápido

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

Solución original (sugerida en la pregunta) - 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 }
}

Cadena válida - 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%

más profundo - 86%

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

payasos tristes - 100% - más 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
fuente
16
Cabe señalar que cuanto más% tiene una prueba, más lenta es
avalancha1
2
¿Qué hay de lodash _.get()? ¿Cuán eficiente es en comparación con esas respuestas?
beniutek
1
Cada método de estos es más lento o más rápido que otros, dependiendo de la situación. Si se encuentran todas las claves, entonces "Object Wrap" podría ser más rápido, pero si no se encuentra una de las claves, entonces "Solución nativa / Solución original" podría ser más rápida.
evilReiko
1
@evilReiko Cualquier método será más lento si no se encuentran claves, pero en proporción entre sí sigue siendo prácticamente el mismo. Sin embargo, tiene razón: esto es más un ejercicio intelectual que cualquier otra cosa. Estamos hablando de un millón de iteraciones por milisegundo aquí. No veo ningún caso de uso en el que haga mucha diferencia. Yo personalmente iría por reduceo try/catchpor conveniencia.
unitario
Cuán eficiente es en comparación contry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex
46

Usted puede leer una propiedad de objeto a cualquier profundidad, si se maneja el nombre como una cadena: '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');

Regresa undefinedsi alguno de los segmentos es undefined.

Kennebec
fuente
3
Vale la pena señalar que este método es muy eficaz, al menos en Chrome, en algunos casos supera a la versión modificada @Claudiu de la respuesta seleccionada. Vea la prueba de rendimiento aquí: 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; })
);

Si está codificando en el entorno ES6 (o utilizando 6to5 ), puede aprovechar la sintaxis de la función de flecha :

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

Con respecto al rendimiento, no hay penalización de rendimiento por usar el try..catchbloque si se establece la propiedad. Hay un impacto en el rendimiento si la propiedad no está configurada.

Considere simplemente 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
fuente
2
Creo que el try-catchenfoque es la mejor respuesta. Hay una diferencia filosófica entre consultar un objeto por su tipo, y asumir que la API existe y fallar en consecuencia si no es así. Este último es más apropiado en lenguajes poco escritos. Ver stackoverflow.com/a/408305/2419669 . El try-catchenfoque también es mucho más claro que if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
yangmillstheory
24

qué tal si

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

}
usuario187291
fuente
15
No creo que try / catch sea una buena forma de comprobar la existencia de un objeto: try / catch está destinado a manejar excepciones, no condiciones normales como la prueba aquí. Creo que (typeof foo == "undefined") en cada paso es mejor, y en general, probablemente se requiera una refactorización si está trabajando con propiedades tan anidadas. Además, try / catch provocará una interrupción en Firebug (y en cualquier navegador en el que se active break-on-error) si se produce una excepción.
Sam Dutton el
Voto sobre esto, porque el navegador verificará la existencia dos veces si usa otras soluciones. Digamos que quieres llamar a 'acb = 2'. El navegador tiene que verificar la existencia antes de modificar el valor (de lo contrario, sería un error de memoria detectado por el sistema operativo).
44
La pregunta sigue siendo: ¿cuál es más rápido para los navegadores para configurar un intento de captura o llamar hasOwnProperty()n veces?
14
¿Por qué es esto malo otra vez? Esto me parece más limpio.
Austin Pray
Yo diría: si espera que la propiedad exista, entonces está bien envolverla en un bloque de prueba. Si no existe, es un error. Pero si solo eres perezoso y pones un código regular en el bloque catch para el caso de que la propiedad no exista try / catch se usa incorrectamente. Aquí se requiere un if / else o algo similar.
robsch
18

Respuesta ES6, probada a fondo :)

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

→ ver Codepen con cobertura de prueba completa

Frank Nocke
fuente
Hice que sus pruebas fallaran al establecer el valor del apoyo plano en 0. Debe preocuparse por la coerción de tipo.
germain
@germain ¿Funciona esto para usted? ( ===Comparo explícitamente las diferentes falsificaciones y la prueba agregada. Si tiene una idea mejor, hágamelo saber).
Frank Nocke
Hice que sus pruebas fallaran nuevamente estableciendo el valor de la punta plana en false. Y luego es posible que desee tener un valor en su objeto establecido en undefined(Sé que es extraño pero es JS). Hice un valor falso positivo establecido en '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
¿Puedes bifurcar mi codepen (arriba a la derecha, fácil), corregir y agregar pruebas y enviarme la URL tuya? Gracias =)
Frank Nocke
Huir a una biblioteca de terceros (enorme) ... posible, pero no es mi preferencia.
Frank Nocke
17

También puede usar la propuesta de encadenamiento opcional tc39 junto con babel 7 - tc39-request-optional-chaining

El código se vería así:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);
Goran.it
fuente
Tenga en cuenta que esta sintaxis casi seguramente cambiará, ya que algunos miembros de TC39 tienen objeciones.
jhpratt GOFUNDME RELICENSING
Probablemente, pero esto estará disponible de alguna forma en el tiempo, y eso es lo único que importa ... Es una de las características que más extraño en JS.
Goran.it
11

Intenté un enfoque recursivo:

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

Se ! keys.length ||inicia la recursión para que no ejecute la función sin teclas para probar. Pruebas:

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

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

Lo estoy usando para imprimir una vista html amigable de un grupo de objetos con clave / valores desconocidos, por ejemplo:

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

Creo que el siguiente script ofrece una representación más legible.

declarar una función:

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

luego úsalo así:

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

}

Lo llamo "técnica de payaso triste" porque está usando el signo o (


EDITAR:

Aquí hay una versión para TypeScript

da verificaciones de tipo en tiempo de compilación (así como el intellisense si usa una herramienta como Visual Studio)

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

El uso es el mismo:

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

pero esta vez intellisense funciona!

Además, puede establecer un valor predeterminado:

o(o(o(o(o(test).level1).level2).level3, "none")
VeganHunter
fuente
1
°0o <°(())))><
Daniel W.
1
Me gusta este, porque es honesto y arroja un "indefinido" en tu cara cuando no sabes tu Objecttipo. +1.
1
Siempre y cuando mantenga la declaración en parens, también puede llamarla técnica de payaso feliz (o
Sventies
Gracias setenta. Me encanta tu comentario Es un ángulo bastante agradable para mirar: tales condiciones se usan principalmente en "si" y siempre están rodeadas de corchetes externos. Entonces, sí, en su mayoría es un payaso feliz :)))
VeganHunter
Realmente necesitas estar enamorado de los paréntesis para
elegir
7

crear un proyecto global functiony utilizarlo en todo

prueba esto

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

si condición

if(isExist(()=>obj.test.foo)){
   ....
}
kelvin kantaria
fuente
Funciona genial. Simple y eficiente
gbland777
6

Una forma simple es esta:

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

El try/catchatrapa los casos de cuando cualquiera de la mayor nivel objetos tales como prueba, test.level1, test.level1.level2 no están definidas.

jfriend00
fuente
6

No vi ningún ejemplo de alguien usando Proxies

Entonces se me ocurrió el mío. Lo bueno de esto es que no tienes que interpolar cadenas. En realidad, puede devolver una función de objeto en cadena y hacer algunas cosas mágicas con ella. Incluso puede llamar a funciones y obtener índices de matriz para buscar objetos profundos

El código anterior funciona bien para cosas sincrónicas. Pero, ¿cómo probarías algo que es asíncrono como esta llamada ajax? ¿Cómo se prueba eso? ¿Qué pasa si la respuesta no es json cuando devuelve un error 500 http?

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

Asegúrese de que puede usar async / wait para deshacerse de algunas devoluciones de llamada. Pero, ¿y si pudieras hacerlo aún más mágicamente? algo que se parece a esto:

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

Probablemente se pregunte dónde están todas las promesas y .thencadenas ... esto podría estar bloqueando todo lo que sabe ... pero usando la misma técnica de Proxy con promesa, puede probar la existencia de una ruta compleja profundamente anidada sin escribir una sola función

Sin fin
fuente
Si alguien está interesado, publico la versión asíncrona en npm
Endless
5

En base a esta respuesta , se me ocurrió esta función genérica usando la ES2015cual resolvería el 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
fuente
1
Aquí está mi enfoque finalfunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington,
5

He creado una pequeña función para obtener propiedades de objetos anidados de forma segura.

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

O una versión más simple pero ligeramente ilegible:

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

O incluso más corto pero sin recurrir a la bandera falsa:

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

Tengo prueba con:

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

Y aquí hay algunas pruebas:

// 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 el código con la documentación y las pruebas que he probado, puede consultar mi github gist: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js

V. Sambor
fuente
4

Una versión ES5 más corta de la excelente respuesta de @ 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;
}

Con una prueba similar:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false
mikemaccana
fuente
el único problema con esto es si hay múltiples niveles de claves indefinidas, entonces obtienes un TypeError, por ejemplocheckObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS
1
Un método más adecuado es cada , cuyo valor se puede devolver directamente.
RobG
Tal vez cambiar success = false;a return false. Debería rescatar una vez que sepa que se rompe, nada más profundo puede existir una vez que sea nulo o indefinido. Esto evitaría los errores en los elementos anidados más profundos, ya que obviamente tampoco existen.
Wade
4

Creo que esta es una ligera mejora (se convierte en 1 línea):

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

Esto funciona porque el operador && devuelve el operando final que evaluó (y cortocircuita).

Julius Musseau
fuente
4

Estaba buscando el valor que se devolvería si la propiedad existe, así que modifiqué la respuesta de CMS anterior. Esto es lo que se me ocurrió:

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
fuente
3

La respuesta dada por CMS funciona bien con la siguiente modificación para cheques nulos también

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
fuente
3

Las siguientes opciones se elaboraron a partir de esta respuesta . Mismo árbol para ambos:

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

Dejar de buscar cuando no esté definido

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

Asegure cada nivel uno por uno

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

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

Sé que esta pregunta es antigua, pero quería ofrecer una extensión agregando esto a todos los objetos. Sé que las personas tienden a fruncir el ceño al usar el prototipo de Objeto para la funcionalidad de objetos extendidos, pero no encuentro nada más fácil que hacerlo. Además, ahora está permitido con el 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;
}});

Ahora, para probar cualquier propiedad en cualquier objeto, simplemente puede hacer:

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

Aquí hay un jsfiddle para probarlo, y en particular incluye algo de jQuery que se rompe si modifica el Object.prototype directamente debido a que la propiedad se vuelve enumerable. Esto debería funcionar bien con bibliotecas de terceros.

Brian Sidebotham
fuente
3

Esto funciona con todos los objetos y matrices :)

ex:

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

esta es mi versión mejorada de la respuesta de Brian

Utilicé _has como el nombre de la propiedad porque puede entrar en conflicto con la propiedad has existente (ej .: 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;
}});

Aquí está el violín

adutu
fuente
3

Aquí está mi opinión: la mayoría de estas soluciones ignoran el caso de una matriz anidada como en:

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

Es posible que desee verificar obj.l2[0].k

Con la función a continuación, puedes hacer deeptest('l2[0].k',obj)

La función devolverá verdadero si el objeto existe, falso de lo contrario

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
fuente
3

Ahora también podemos usar reducepara recorrer las claves anidadas:

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

Puede hacer esto usando la función recursiva. Esto funcionará incluso si no conoce el nombre de todas las claves de objeto anidadas.

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
fuente
2

Hay una función aquí en thecodeabode (safeRead) que hará esto de manera segura ... es decir

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

si alguna propiedad es nula o indefinida, se devuelve una cadena vacía

Ben
fuente
Me gusta este método con plantillas porque devuelve una cadena vacía si no se establece
Lounge9
2

Según un comentario anterior , aquí hay otra versión en la que tampoco se pudo definir el objeto principal:

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

Escribí mi propia función que toma la ruta deseada y tiene una función de devolución de llamada buena y mala.

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
fuente
Pensé que era justo darle crédito por la inspiración para adaptar su código a mi respuesta
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
fuente