recorrer objetos objeto new lista keys eliminar elemento buscar array agregar javascript object nested

objetos - Prueba de existencia de clave de objeto JavaScript anidada



object.keys map (30)

Actualizar

Parece que lodash ha agregado _.get para todas las necesidades de sus propiedades anidadas.

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

lodash.com/docs#get

Respuesta anterior

lodash 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 basado en la ruta descrita por las claves dadas. Las claves pueden darse como una matriz o como una cadena separada por puntos. Devuelve undefined si 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

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 probar la existencia de claves en los objetos más profundamente anidados?

alert(test.level1); produce 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.


ES6 respuesta, probado 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


Ahora también podemos usar reduce para 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)


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

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

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


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

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


Creo que esto es una ligera mejora (se convierte en un 1-liner):

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

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


Creo que la siguiente secuencia de comandos da una representación más legible.

declarar una función:

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

entonces úsalo así:

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

Lo llamo "técnica de payaso triste" porque está usando la señal o (

EDITAR:

Aquí hay una versión para TypeScript.

da verificaciones de tipos en tiempo de compilación (así como también 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 el intellisense funciona!

Además, puede establecer un valor predeterminado:

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


Debe hacerlo paso a paso si no desea un TypeError , porque si uno de los miembros es null o undefined está undefined , e intenta acceder a un miembro, se lanzará una excepción.

Puede simplemente catch la 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


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


Esta es 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 comprobar obj.l2[0].k

Con la siguiente función, puede 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));


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


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

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

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


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

Descargo de responsabilidad # 1 Convertir cadenas en referencias es una meta-programación innecesaria y probablemente sea mejor evitarla. No pierdas de vista tus referencias para empezar. Lea más de esta respuesta a una pregunta similar .

Renuncia # 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 sabiendo las limitaciones de cada uno. Para mi iría con algo como reduce por 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 } }

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%

deeptest - 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% - mas 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);


Intenté un enfoque recursivo:

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

El ! keys.length || arranca de 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 montón de objetos con claves / valores desconocidos, por ejemplo:

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


La respuesta dada por CMS funciona bien con la siguiente modificación para las comprobaciones nulas 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; }


Las siguientes opciones fueron elaboradas a partir de esta respuesta . El mismo árbol para ambos:

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

Deja 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

Asegura cada nivel uno por uno

var $ = function (empty) { return function (node) { return node || empty; }; }({}); $($(o.a).b).c // 1 $($(o.x).y).z // undefined


No vi ningún ejemplo de alguien que usa Proxies

Así que se me ocurrió la mía. Lo mejor de esto es que no tienes que interpolar cadenas. Realmente puedes devolver una función de objeto capaz de encadenar y hacer algunas cosas mágicas con ella. Incluso puede llamar a funciones y obtener índices de matriz para verificar objetos profundos

function resolve(target) { var noop = () => {} // We us a noop function so we can call methods also return new Proxy(noop, { get(noop, key) { // return end result if key is _result return key === ''_result'' ? target : resolve( // resolve with target value or undefined target === undefined ? undefined : target[key] ) }, // if we want to test a function then we can do so alos thanks to using noop // instead of using target in our proxy apply(noop, that, args) { return resolve(typeof target === ''function'' ? target.apply(that, args) : undefined) }, }) } // some modified examples from the accepted answer var test = {level1: {level2:() => ({level3:''level3''})}} var test1 = {key1: {key2: [''item0'']}} // You need to get _result in the end to get the final result console.log(resolve(test).level1.level2().level3._result) console.log(resolve(test).level1.level2().level3.level4.level5._result) console.log(resolve(test1).key1.key2[0]._result) console.log(resolve(test1)[0].key._result) // don''t exist

El código anterior funciona bien para cosas síncronas. Pero, ¿cómo probaría 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 de 500 http?

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

seguro que podría usar async / await 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 te preguntes dónde están todas las promesas y .then cadenas ... esto podría estar bloqueando todo lo que sabes ... pero al usar la misma técnica de Proxy con la promesa, puedes probar un camino complejo profundamente anidado para su existencia sin tener que escribir un función única

function resolve(target) { return new Proxy(() => {}, { get(noop, key) { return key === ''then'' ? target.then.bind(target) : resolve( Promise.resolve(target).then(target => { if (typeof target[key] === ''function'') return target[key].bind(target) return target[key] }) ) }, apply(noop, that, args) { return resolve(target.then(result => { return result.apply(that, args) })) }, }) } // this feels very much synchronous but are still non blocking :) resolve(window) // this will chain a noop function until you call then() .fetch(''https://httpbin.org/get'') .json() .headers[''User-Agent''] .then(console.log, console.warn) // you get a warning if it doesn''t exist // You could use this method also for the first test object // also, but it would have to call .then() in the end // Another example resolve(window) .fetch(''https://httpbin.org/get?items=4&items=2'') .json() .args .items // nice that you can map an array item without even having it ready .map(n => ~~n * 4) .then(console.log, console.warn) // you get a warning if it doesn''t exist


Pensé que agregaría otro que se me ocurrió hoy. La razón por la que me siento orgulloso de esta solución es que evita los soportes anidados que se utilizan en muchas soluciones como Object Wrap (por Oliver Steele) :

(en este ejemplo, uso un guión bajo como una variable de marcador de posición, pero cualquier nombre de variable funcionará)

//the ''test'' object var test = {level1: {level2: {level3: ''level3''}}}; let _ = test; if ((_=_.level1) && (_=_.level2) && (_=_.level3)) { let level3 = _; //do stuff with level3 }

//you could also use ''stacked'' if statements. This helps if your object goes very deep. //(formatted without nesting or curly braces except the last one) let _ = test; if (_=_.level1) if (_=_.level2) if (_=_.level3) { let level3 = _; //do stuff with level3 } //or you can indent: if (_=_.level1) if (_=_.level2) if (_=_.level3) { let level3 = _; //do stuff with level3 }


Puede leer una propiedad de objeto a cualquier profundidad, si 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'');

Devuelve undefined si alguno de los segmentos undefined está undefined .


Sé que esta pregunta es antigua, pero quería ofrecer una extensión agregando esto a todos los objetos. Sé que la gente tiende a fruncir el ceño al usar el prototipo de Objeto para la funcionalidad extendida de objetos, pero no encuentro nada más fácil que hacer esto. Además, ahora se permite 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 algunos jQuery que se rompen si modifica el Object.prototype directamente debido a que la propiedad se vuelve enumerable. Esto debería funcionar bien con bibliotecas de terceros.


Sobre la base de esta respuesta , se me ocurrió esta función genérica utilizando ES2015 que 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 ); }


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

El código se vería así:

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


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/catch captura los casos en los que no se ha definido ninguno de los objetos de nivel superior, como test, test.level1, test.level1.level2.


Una versión más corta, ES5 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


qué tal si

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


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 existente (por ejemplo: maps)

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) { var obj = this; var needles = needle.split( "." ); var needles_full=[]; var needles_square; for( var i = 0; i<needles.length; i++ ) { needles_square = needles[i].split( "[" ); if(needles_square.length>1){ for( var j = 0; j<needles_square.length; j++ ) { if(needles_square[j].length){ needles_full.push(needles_square[j]); } } }else{ needles_full.push(needles[i]); } } for( var i = 0; i<needles_full.length; i++ ) { var res = needles_full[i].match(/^((/d+)|"(.+)"|''(.+)'')/]$/); if (res != null) { for (var j = 0; j < res.length; j++) { if (res[j] != undefined) { needles_full[i] = res[j]; } } } if( typeof obj[needles_full[i]]==''undefined'') { return false; } obj = obj[needles_full[i]]; } return true; }});

Aquí está el fiddle


Edición ligera a esta respuesta para permitir matrices anidadas en la ruta

var has = function (obj, key) { return key.split(".").every(function (x) { if (typeof obj != "object" || obj === null || !x in obj) return false; if (obj.constructor === Array) obj = obj[0]; obj = obj[x]; return true; }); }

Compruebe la respuesta vinculada para usos :)


Tenía el mismo problema y quería ver si podía encontrar una solución propia. Esto acepta la ruta que desea verificar como una cadena.

function checkPathForTruthy(obj, path) { if (//[[a-zA-Z_]/.test(path)) { console.log("Cannot resolve variables in property accessors"); return false; } path = path.replace(//[/g, "."); path = path.replace(/]|''|"/g, ""); path = path.split("."); var steps = 0; var lastRef = obj; var exists = path.every(key => { var currentItem = lastRef[path[steps]]; if (currentItem) { lastRef = currentItem; steps++; return true; } else { return false; } }); return exists; }

Aquí hay un fragmento con algunos casos de registro y prueba:

console.clear(); var testCases = [ ["data.Messages[0].Code", true], ["data.Messages[1].Code", true], ["data.Messages[0][''Code'']", true], [''data.Messages[0]["Code"]'', true], ["data[Messages][0][''Code'']", false], ["data[''Messages''][0][''Code'']", true] ]; var path = "data.Messages[0].Code"; var obj = { data: { Messages: [{ Code: "0" }, { Code: "1" }] } } function checkPathForTruthy(obj, path) { if (//[[a-zA-Z_]/.test(path)) { console.log("Cannot resolve variables in property accessors"); return false; } path = path.replace(//[/g, "."); path = path.replace(/]|''|"/g, ""); path = path.split("."); var steps = 0; var lastRef = obj; var logOutput = []; var exists = path.every(key => { var currentItem = lastRef[path[steps]]; if (currentItem) { logOutput.push(currentItem); lastRef = currentItem; steps++; return true; } else { return false; } }); console.log(exists, logOutput); return exists; } testCases.forEach(testCase => { if (checkPathForTruthy(obj, testCase[0]) === testCase[1]) { console.log("Passed: " + testCase[0]); } else { console.log("Failed: " + testCase[0] + " expected " + testCase[1]); } });


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


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

En cuanto al rendimiento, no hay ninguna penalización de rendimiento por usar el bloque try..catch si la propiedad está establecida. Hay un impacto en el rendimiento si la propiedad no está establecida.

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