ramda property objects how javascript jquery path nested

property - Acceso a objetos JavaScript anidados con clave de cadena



nested map javascript (28)

Trabajar con la property Underscore o propertyOf :

var test = { foo: { bar: { baz: ''hello'' } } } var string = ''foo.bar.baz''; // document.write(_.propertyOf(test)(string.split(''.''))) document.write(_.property(string.split(''.''))(test));

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

Buena suerte...

Tengo una estructura de datos como esta:

var someObject = { ''part1'' : { ''name'': ''Part 1'', ''size'': ''20'', ''qty'' : ''50'' }, ''part2'' : { ''name'': ''Part 2'', ''size'': ''15'', ''qty'' : ''60'' }, ''part3'' : [ { ''name'': ''Part 3A'', ''size'': ''10'', ''qty'' : ''20'' }, { ''name'': ''Part 3B'', ''size'': ''5'', ''qty'' : ''20'' }, { ''name'': ''Part 3C'', ''size'': ''7.5'', ''qty'' : ''20'' } ] };

Y me gustaría acceder a los datos usando estas variables:

var part1name = "part1.name"; var part2quantity = "part2.qty"; var part3name1 = "part3[0].name";

part1name debe rellenarse con el valor de someObject.part1.name , que es "Part 1". Lo mismo con part2quantity que llenó con 60.

¿Hay alguna forma de lograr esto con javascript puro o JQuery?


¿Qué pasa con esta solución:

setJsonValue: function (json, field, val) { if (field !== undefined){ try { eval("json." + field + " = val"); } catch(e){ ; } } }

Y este, por conseguir:

getJsonValue: function (json, field){ var value = undefined; if (field !== undefined) { try { eval("value = json." + field); } catch(e){ ; } } return value; };

Probablemente algunos los considerarán inseguros, pero deben ser mucho más rápidos que ellos, analizando la cadena.


Acabo de hacer esto basado en un código similar que ya tenía, parece funcionar:

Object.byString = function(o, s) { s = s.replace(//[(/w+)/]/g, ''.$1''); // convert indexes to properties s = s.replace(/^/./, ''''); // strip a leading dot var a = s.split(''.''); for (var i = 0, n = a.length; i < n; ++i) { var k = a[i]; if (k in o) { o = o[k]; } else { return; } } return o; }

Uso::

Object.byString(someObj, ''part3[0].name'');

Vea una demostración de trabajo en http://jsfiddle.net/alnitak/hEsys/

EDITAR algunos han notado que este código generará un error si se pasa una cadena en la que los índices más a la izquierda no corresponden a una entrada correctamente anidada dentro del objeto. Esta es una preocupación válida, pero en mi humilde opinión se aborda mejor con un bloque try / catch al llamar, en lugar de que esta función devuelva silenciosamente undefined para un índice no válido.



Aquí ofrezco más formas, que parecen más rápidas en muchos aspectos:

Opción 1: dividir cadena en. o [o] o ''o ", inviértalo, omita los elementos vacíos.

function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== ''string'') path = '''' + path; var parts = path.split(//[|/]|/.|''|"/g).reverse(), name; // (why reverse? because it''s usually faster to pop off the end of an array) while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; } return origin; }

Opción 2 (el más rápido de todos, excepto el de eval ): escaneo de caracteres de bajo nivel (no regex / split / etc, solo un escaneo de caracteres rápido). Nota: Este no admite cotizaciones para índices.

function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== ''string'') path = '''' + path; var c = '''', pc, i = 0, n = path.length, name = ''''; if (n) while (i<=n) ((c = path[i++]) == ''.'' || c == ''['' || c == '']'' || c == void 0) ? (name?(origin = origin[name], name = ''''):(pc==''.''||pc==''[''||pc=='']''&&c=='']''?i=n+2:void 0),pc=c) : name += c; if (i==n+2) throw "Invalid path: "+path; return origin; } // (around 1,000,000+/- ops/sec)

Opción 3: ( nuevo : la opción 2 se expandió para admitir citas, un poco más lenta pero rápida)

function getValue(path, origin) { if (origin === void 0 || origin === null) origin = self ? self : this; if (typeof path !== ''string'') path = '''' + path; var c, pc, i = 0, n = path.length, name = '''', q; while (i<=n) ((c = path[i++]) == ''.'' || c == ''['' || c == '']'' || c == "''" || c == ''"'' || c == void 0) ? (c==q&&path[i]=='']''?q='''':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='''') : (pc==''[''&&(c==''"''||c=="''")?q=c:pc==''.''||pc==''[''||pc=='']''&&c=='']''||pc==''"''||pc=="''"?i=n+2:void 0), pc=c) : name += c; if (i==n+2 || name) throw "Invalid path: "+path; return origin; }

JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3

Sin embargo, "eval (...)" sigue siendo el rey (es decir, el rendimiento). Si tiene rutas de propiedad directamente bajo su control, no debería haber ningún problema con el uso de ''eval'' (especialmente si se desea velocidad). Si tira de rutas de propiedad "sobre el cable" ( en la línea ! Lol: P), entonces sí, use algo más para estar seguro. Solo un idiota diría que nunca uses "eval" en absoluto, ya que hay buenas razones para usarlo. Además, "se usa en el analizador JSON de Doug Crockford ". Si la entrada es segura, entonces no hay problemas en absoluto. Utilice la herramienta adecuada para el trabajo correcto, eso es todo.


Creo que estás pidiendo esto:

var part1name = someObject.part1.name; var part2quantity = someObject.part2.qty; var part3name1 = someObject.part3[0].name;

Usted podría estar pidiendo esto:

var part1name = someObject["part1"]["name"]; var part2quantity = someObject["part2"]["qty"]; var part3name1 = someObject["part3"][0]["name"];

Ambos de los cuales funcionarán

O tal vez estas pidiendo esto

var partName = "part1"; var nameStr = "name"; var part1name = someObject[partName][nameStr];

Finalmente podrías estar preguntando por esto.

var partName = "part1.name"; var partBits = partName.split("."); var part1name = someObject[partBits[0]][partBits[1]];


El enfoque de Speigg es muy limpio y ordenado, aunque encontré esta respuesta mientras buscaba la solución para acceder a las propiedades de AngularJS $ scope por la ruta de la cadena y, con una pequeña modificación, hizo el trabajo:

$scope.resolve = function( path, obj ) { return path.split(''.'').reduce( function( prev, curr ) { return prev[curr]; }, obj || this ); }

Simplemente coloque esta función en su controlador raíz y utilícela en cualquier ámbito secundario como este:

$scope.resolve( ''path.to.any.object.in.scope'')


En lugar de una cadena, se puede usar una matriz para direccionar objetos y matrices anidadas, por ejemplo: ["my_field", "another_field", 0, "last_field", 10]

Aquí hay un ejemplo que cambiaría un campo basado en esta representación de matriz. Estoy usando algo así en react.js para campos de entrada controlados que cambian el estado de las estructuras anidadas.

let state = { test: "test_value", nested: { level1: "level1 value" }, arr: [1, 2, 3], nested_arr: { arr: ["buh", "bah", "foo"] } } function handleChange(value, fields) { let update_field = state; for(var i = 0; i < fields.length - 1; i++){ update_field = update_field[fields[i]]; } update_field[fields[fields.length-1]] = value; } handleChange("update", ["test"]); handleChange("update_nested", ["nested","level1"]); handleChange(100, ["arr",0]); handleChange(''changed_foo'', ["nested_arr", "arr", 3]); console.log(state);


Es un trazador de líneas con lodash.

const deep = { l1: { l2: { l3: "Hello" } } }; const prop = "l1.l2.l3"; const val = _.reduce(prop.split(''.''), function(result, value) { return result ? result[value] : undefined; }, deep); // val === "Hello"

O mejor...

const val = _.get(deep, prop);

O la versión ES6 w / reducir ...

const val = prop.split(''.'').reduce((r, val) => { return r ? r[val] : undefined; }, deep);

Plunkr


Esta es la solución que uso:

function resolve(path, obj=self, separator=''.'') { var properties = Array.isArray(path) ? path : path.split(separator) return properties.reduce((prev, curr) => prev && prev[curr], obj) }

Ejemplo de uso:

// accessing property path on global scope resolve("document.body.style.width") // or resolve("style.width", document.body) // accessing array indexes // (someObject has been defined in the question) resolve("part3.0.size", someObject) // returns ''10'' // accessing non-existent properties // returns undefined when intermediate properties are not defined: resolve(''properties.that.do.not.exist'', {hello:''world''}) // accessing properties with unusual keys by changing the separator var obj = { object: { ''a.property.name.with.periods'': 42 } } resolve(''object->a.property.name.with.periods'', obj, ''->'') // returns 42 // accessing properties with unusual keys by passing a property name array resolve([''object'', ''a.property.name.with.periods''], obj) // returns 42

Limitaciones:

  • No se pueden usar corchetes ( [] ) para los índices de matriz, aunque la especificación de índices de matriz entre el token separador (por ejemplo,) funciona bien como se muestra arriba.

Esto ahora es soportado por lodash usando _.get(obj, property) . Ver https://lodash.com/docs#get

Ejemplo de la documentación:

var object = { ''a'': [{ ''b'': { ''c'': 3 } }] }; _.get(object, ''a[0].b.c''); // → 3 _.get(object, [''a'', ''0'', ''b'', ''c'']); // → 3 _.get(object, ''a.b.c'', ''default''); // → ''default''


Esto probablemente nunca verá la luz del día ... pero aquí está de todos modos.

  1. Reemplace la sintaxis del corchete [] con .
  2. Dividir en personaje
  3. Quitar cuerdas en blanco
  4. Encuentra el camino (de lo contrario undefined )

// "one liner" (ES6) const deep_value = (obj, path) => path .replace(//[|/]/.?/g, ''.'') .split(''.'') .filter(s => s) .reduce((acc, val) => acc && acc[val], obj); // ... and that''s it. var someObject = { ''part1'' : { ''name'': ''Part 1'', ''size'': ''20'', ''qty'' : ''50'' }, ''part2'' : { ''name'': ''Part 2'', ''size'': ''15'', ''qty'' : ''60'' }, ''part3'' : [ { ''name'': ''Part 3A'', ''size'': ''10'', ''qty'' : ''20'' } // ... ] }; console.log(deep_value(someObject, "part1.name")); // Part 1 console.log(deep_value(someObject, "part2.qty")); // 60 console.log(deep_value(someObject, "part3[0].name")); // Part 3A


Función simple, que permite una cadena o una ruta de matriz.

function get(obj, path) { if(typeof path === ''string'') path = path.split(''.''); if(path.length === 0) return obj; return get(obj[path[0]], path.slice(1)); } const obj = {a: {b: {c: ''foo''}}}; console.log(get(obj, ''a.b.c'')); //foo

O

console.log(get(obj, [''a'', ''b'', ''c''])); //foo


Funciona para matrices / matrices dentro del objeto también. Defensiva contra valores inválidos.

/** * Retrieve nested item from object/array * @param {Object|Array} obj * @param {String} path dot separated * @param {*} def default value ( if result undefined ) * @returns {*} */ function path(obj, path, def){ var i, len; for(i = 0,path = path.split(''.''), len = path.length; i < len; i++){ if(!obj || typeof obj !== ''object'') return def; obj = obj[path[i]]; } if(obj === undefined) return def; return obj; } ////////////////////////// // TEST // ////////////////////////// var arr = [true, {''sp ace'': true}, true] var obj = { ''sp ace'': true, arr: arr, nested: {''dotted.str.ing'': true}, arr3: arr } shouldThrow(`path(obj, "arr.0")`); shouldBeDefined(`path(obj, "arr[0]")`); shouldBeEqualToNumber(`path(obj, "arr.length")`, 3); shouldBeTrue(`path(obj, "sp ace")`); shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback"); shouldBeTrue(`path(obj, "nested[''dotted.str.ing''])`);

<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>


Hace poco tuve la misma pregunta y usé con éxito https://npmjs.org/package/tea-properties que también set arrays / objetos anidados:

obtener:

var o = { prop: { arr: [ {foo: ''bar''} ] } }; var properties = require(''tea-properties''); var value = properties.get(o, ''prop.arr[0].foo''); assert(value, ''bar''); // true

conjunto:

var o = {}; var properties = require(''tea-properties''); properties.set(o, ''prop.arr[0].foo'', ''bar''); assert(o.prop.arr[0].foo, ''bar''); // true


Las soluciones aquí son solo para acceder a las claves profundamente anidadas. Necesitaba uno para acceder, agregar, modificar y eliminar las claves. Esto es lo que se me ocurrió:

var deepAccessObject = function(object, path_to_key, type_of_function, value){ switch(type_of_function){ //Add key/modify key case 0: if(path_to_key.length === 1){ if(value) object[path_to_key[0]] = value; return object[path_to_key[0]]; }else{ if(object[path_to_key[0]]) return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value); else object[path_to_key[0]] = {}; } break; //delete key case 1: if(path_to_key.length === 1){ delete object[path_to_key[0]]; return true; }else{ if(object[path_to_key[0]]) return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value); else return false; } break; default: console.log("Wrong type of function"); } };

  • path_to_key : ruta en una matriz. Puede reemplazarlo por su string_path.split(".") .
  • type_of_function : 0 para acceder (no pase ningún valor a value ), 0 para agregar y modificar. 1 para eliminar.

Partiendo de la respuesta de Alnitak:

if(!Object.prototype.byString){ //NEW byString which can update values Object.prototype.byString = function(s, v, o) { var _o = o || this; s = s.replace(//[(/w+)/]/g, ''.$1''); // CONVERT INDEXES TO PROPERTIES s = s.replace(/^/./, ''''); // STRIP A LEADING DOT var a = s.split(''.''); //ARRAY OF STRINGS SPLIT BY ''.'' for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS var k = a[i]; if (k in _o) {//LOOP THROUGH OBJECT KEYS if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM if(i === a.length -1){//IF IT''S THE LAST IN THE ARRAY _o[k] = v; } } _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE } } else { return; } } return _o; };

}

¡Esto te permite establecer un valor también!

He creado un paquete github y github con esto también


Por si acaso, cualquiera que visite esta pregunta en 2017 o más adelante y busque una manera fácil de recordar , aquí hay una publicación de blog elaborada sobre Acceso a objetos anidados en JavaScript sin ser engañada por

No se puede leer la propiedad ''foo'' del error indefinido

Acceder a objetos anidados usando matriz reducir

Tomemos esta estructura de ejemplo

var access = require(''safe-access''); access(very, ''nested.property.and.array[0]'');

Para poder acceder a matrices anidadas, puede escribir su propia utilidad de reducción de matriz.

const user = { id: 101, email: ''[email protected]'', personalInfo: { name: ''Jack'', address: [{ line1: ''westwish st'', line2: ''washmasher'', city: ''wallas'', state: ''WX'' }] } }

También hay un excelente typy tipo de biblioteca typy que hace todo esto por ti.

Con typy, tu código se verá así

const getNestedObject = (nestedObj, pathArr) => { return pathArr.reduce((obj, key) => (obj && obj[key] !== ''undefined'') ? obj[key] : undefined, nestedObj); } // pass in your object structure as array elements const name = getNestedObject(user, [''personalInfo'', ''name'']); // to access nested array, just pass in array index as an element the path array. const city = getNestedObject(user, [''personalInfo'', ''address'', 0, ''city'']); // this will return the city from the first address item.

Descargo de responsabilidad: Soy el autor de este paquete.


Puede administrar para obtener el valor de un miembro de objeto profundo con notación de puntos sin ninguna biblioteca externa de JavaScript con el siguiente truco:

new Function(''_'', ''return _.'' + path)(obj);

En su caso, para obtener el valor de part1.name de someObject simplemente haga:

new Function(''_'', ''return _.part1.name'')(someObject);

Aquí hay una simple demostración de violín: https://jsfiddle.net/harishanchu/oq5esowf/


Si bien reducir es bueno, me sorprende que nadie haya usado para cada uno:

function valueForKeyPath(obj, path){ const keys = path.split(''.''); keys.forEach((key)=> obj = obj[key]); return obj; };

Test


Si necesita acceder a diferentes claves anidadas sin saberlo en el momento de la codificación (será trivial abordarlas) puede usar el acceso a la notación de matriz:

var part1name = someObject[''part1''][''name'']; var part2quantity = someObject[''part2''][''qty'']; var part3name1 = someObject[''part3''][0][''name''];

Son equivalentes al acceso de notación de puntos y pueden variar en el tiempo de ejecución, por ejemplo:

var part = ''part1''; var property = ''name''; var part1name = someObject[part][property];

es equivalente a

var part1name = someObject[''part1''][''name''];

o

var part1name = someObject.part1.name;

Espero que esta dirija tu pregunta ...

EDITAR

No usaré una cadena para mantener una especie de consulta xpath para acceder a un valor de objeto. Como tiene que llamar a una función para analizar la consulta y recuperar el valor, seguiría otra ruta (no:

var part1name = function(){ return this.part1.name; } var part2quantity = function() { return this[''part2''][''qty'']; } var part3name1 = function() { return this.part3[0][''name''];} // usage: part1name.apply(someObject);

o, si no te gusta el método de aplicación

var part1name = function(obj){ return obj.part1.name; } var part2quantity = function(obj) { return obj[''part2''][''qty'']; } var part3name1 = function(obj) { return obj.part3[0][''name''];} // usage: part1name(someObject);

Las funciones son más cortas, más claras, el intérprete las comprueba en busca de errores de sintaxis, etc.

Por cierto, creo que una tarea simple realizada en el momento adecuado será suficiente ...


Sobre la base de una respuesta anterior, he creado una función que también puede manejar los paréntesis. Pero no hay puntos dentro de ellos debido a la división.

function get(obj, str) { return str.split(//.|/[/g).map(function(crumb) { return crumb.replace(//]$/, '''').trim().replace(/^(["''])((?:(?!/1)[^//]|//.)*?)/1$/, (match, quote, str) => str.replace(///(//)?/g, "$1")); }).reduce(function(obj, prop) { return obj ? obj[prop] : undefined; }, obj); }


Tendrías que analizar la cuerda tú mismo:

function getProperty(obj, prop) { var parts = prop.split(''.''); if (Array.isArray(parts)) { var last = parts.pop(), l = parts.length, i = 1, current = parts[0]; while((obj = obj[current]) && i < l) { current = parts[i]; i++; } if(obj) { return obj[last]; } } else { throw ''parts is not valid array''; } }

Esto requería que también definiera índices de matriz con notación de puntos:

var part3name1 = "part3.0.name";

Facilita el análisis.

DEMO


Todavía no he encontrado un paquete para realizar todas las operaciones con una ruta de cadena, así que terminé escribiendo mi propio pequeño paquete rápido que admite insertar (), obtener () (con retorno predeterminado), establecer () y eliminar ( ) operaciones.

Puede usar la notación de puntos, los corchetes, los índices numéricos, las propiedades de los números de cadena y las claves con caracteres que no son palabras. Uso simple a continuación:

> var jsocrud = require(''jsocrud''); ... // Get (Read) --- > var obj = { > foo: [ > { > ''key w/ non-word chars'': ''bar'' > } > ] > }; undefined > jsocrud.get(obj, ''.foo[0]["key w/ non-word chars"]''); ''bar''

https://www.npmjs.com/package/jsocrud

https://github.com/vertical-knowledge/jsocrud


utilizando eval:

var part1name = eval("someObject.part1.name");

envolver para devolver indefinido en error

function path(obj, path) { try { return eval("obj." + path); } catch(e) { return undefined; } }

http://jsfiddle.net/shanimal/b3xTw/

Utilice el sentido común y la precaución cuando ejerza el poder de eval. Es un poco como un sable ligero, si lo enciendes hay un 90% de probabilidad de que te rompas una extremidad. No es para todos.


ES6 : solo una línea en Vanila JS (devuelve nulo si no encuentra en lugar de dar error):

''path.string''.split(''.'').reduce((p,c)=>p&&p[c]||null, MyOBJ)

o ejemplo:

''a.b.c''.split(''.'').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})

Para una función lista para usar que también reconoce falso, 0 y número negativo y acepta valores predeterminados como parámetro:

const resolvePath = (object, path, defaultValue) => path .split(''.'') .reduce((o, p) => o ? o[p] : defaultValue, object)

Ejemplo de uso:

resolvePath(window,''document.body'') => <body> resolvePath(window,''document.body.xyz'') => undefined resolvePath(window,''document.body.xyz'', null) => null resolvePath(window,''document.body.xyz'', 1) => 1

Bonus :

Para establecer un camino (solicitado por @ rob-gordon) puede usar:

const setPath = (object, path, value) => path .split(''.'') .reduce((o,p) => o[p] = path.split(''.'').pop() === p ? value : o[p] || {}, object)

Ejemplo:

let myVar = {} setPath(myVar, ''a.b.c'', 42) => 42 console.log(myVar) => {a: {b: {c: 42}}}

Acceda a la matriz con [] :

const resolvePath = (object, path, defaultValue) => path .split(/[/./[/]/'/"]/) .filter(p => p) .reduce((o, p) => o ? o[p] : defaultValue, object)

ejemplo

const myVar = {a:{b:[{c:1}]}} resolvePath(myVar,''a.b[0].c'') => 1 resolvePath(myVar,''a["b"][/'0/'].c'') => 1


// (IE9+) Two steps var pathString = "[0][''property''].others[3].next[''final'']"; var obj = [{ property: { others: [1, 2, 3, { next: { final: "SUCCESS" } }] } }]; // Turn string to path array var pathArray = pathString .replace(//[["'']?([/w]+)["'']?/]/g,".$1") .split(".") .splice(1); // Add object prototype method Object.prototype.path = function (path) { try { return [this].concat(path).reduce(function (f, l) { return f[l]; }); } catch (e) { console.error(e); } }; // usage console.log(obj.path(pathArray)); console.log(obj.path([0,"doesNotExist"]));


/** * Access a deep value inside a object * Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz" * @author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e * Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/ */ function getDeepVal(obj, path) { if (typeof obj === "undefined" || obj === null) return; path = path.split(/[/./[/]/"/']{1,2}/); for (var i = 0, l = path.length; i < l; i++) { if (path[i] === "") continue; obj = obj[path[i]]; if (typeof obj === "undefined" || obj === null) return; } return obj; }

Funciona con

getDeepVal(obj,''foo.bar'') getDeepVal(obj,''foo.1.bar'') getDeepVal(obj,''foo[0].baz'') getDeepVal(obj,''foo[1][2]'') getDeepVal(obj,"foo[''bar''].baz") getDeepVal(obj,"foo[''bar''][''baz'']") getDeepVal(obj,"foo.bar.0.baz[1][''2''][''w''].aaa[/"f/"].bb")