Acceso a objetos de JavaScript anidados y arays por ruta de cadena

454

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 someObject.part1.nameel valor de, que es "Parte 1". Lo mismo con la cantidad de part2 que se llenó con 60.

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

Komaruloh
fuente
¿No estás seguro de lo que estás preguntando aquí? ¿Desea poder consultar part1.name y obtener el texto "part1.name"? ¿O desea un medio para obtener el valor almacenado en part1.name?
BonyT
has intentado hacer como var part1name = someObject.part1name;`
Rafay
1
@BonyT: quiero consultar someObject.part1.name y devolver el valor ("Parte 1"). Sin embargo, quiero que la consulta (la llamé "la clave") se almacene en una variable 'part1name'. Gracias por su respuesta. @ 3nigma: Ciertamente lo he hecho. Pero esa no es mi intención. Gracias por la respuesta.
Komaruloh
1
en la respuesta duplicada, me encanta la respuesta de fyr stackoverflow.com/questions/8817394/…
Steve Black

Respuestas:

520

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 en funcionamiento en http://jsfiddle.net/alnitak/hEsys/

EDITAR algunos han notado que este código arrojará un error si se pasa una cadena donde los índices del extremo izquierdo no corresponden a una entrada correctamente anidada dentro del objeto. Esta es una preocupación válida, pero en mi humilde opinión, es mejor abordarla con un try / catchbloqueo al llamar, en lugar de hacer que esta función regrese en silencio undefinedpara un índice no válido.

Alnitak
fuente
19
Esto funciona de maravilla. Contribuya esto a Internet envolviéndolo como un paquete de nodos.
t3dodson
14
@ t3dodson que acabo de hacer: github.com/capaj/object-resolve-path solo tenga en cuenta que esto no funciona bien cuando el nombre de su propiedad contiene '[]' en sí mismo. Regex lo reemplazará con '.' y no funciona como se esperaba
Capaj
20
Buena cosa; el uso de la biblioteca lodash, también se puede hacer:_.get(object, nestedPropertyString);
Ian
17
Esto probablemente se perderá en el mar de comentarios, sin embargo, es un error si intenta abordar una propiedad que no existe. Por lo tanto 'part3[0].name.iDontExist'. Agregar una verificación para ver si oes un objeto en el if insoluciona el problema. (Cómo lo haces depende de ti). Ver violín actualizado: jsfiddle.net/hEsys/418
ste2425
2
Esto es muy dorado. Tenemos una aplicación basada en la configuración y esto es un poco útil. ¡Gracias!
Christian Esperar
182

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 especificar índices de matriz entre el token separador (por ejemplo, .) funciona bien como se muestra arriba.
Speigg
fuente
77
usar reduce es una solución excelente (también se puede usar _.reduce()desde la biblioteca de subrayado o lodash)
Alp
3
Creo que selfprobablemente no esté definido aquí. Qué quiere decir this?
Platinum Azure
2
Aquí está mi complemento para establecer valores por ruta: pastebin.com/jDp5sKT9
mroach
1
¿Alguien sabe cómo portar esto a TypeScript?
Adam Plocher
1
@ SC1000 buena idea. Esta respuesta se escribió antes de que los parámetros predeterminados estuvieran disponibles en la mayoría de los navegadores. Lo actualizaré a "function resolve (path, obj = self)", ya que hacer referencia al objeto global como predeterminado es intencional.
speigg
179

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

Ejemplo de los documentos:

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'
Ian Walker-Sperber
fuente
99
Esta debería ser la única respuesta aceptada, porque esta es la única que funciona para la sintaxis de punto y paréntesis y no falla, cuando tenemos '[]' en la cadena de una clave en la ruta.
Capaj
77
Esta. Además, es compatible_.set(...)
Josh C.
¿Qué sucede si no se encuentra el objeto?
DDave
@DDave si el valor pasado como el objeto no está definido o no es un objeto, _.getmostrará el mismo comportamiento que cuando no se encuentra ninguna clave en el objeto proporcionado. por ejemplo _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Sin embargo, este comportamiento no está definido en los documentos, por lo que está sujeto a cambios.
Ian Walker-Sperber
55
@Capaj, ¿estás bromeando? ¿Y quién no quiere / no puede usar lodash?
Andre Figueiredo
74

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

Con operador de encadenamiento opcional :

'a.b.c'.split('.').reduce((p,c)=>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 a usar:

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

bono :

Para establecer una ruta (solicitada por @ rob-gordon) puede usar:

const setPath = (object, path, value) => path
   .split('.')
   .reduce((o,p,i) => o[p] = path.split('.').length === ++i ? 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
Adriano Spadoni
fuente
2
Amo esta técnica Esto es realmente desordenado, pero quería usar esta técnica para la asignación. let o = {a:{b:{c:1}}}; let str = 'a.b.c'; str.split('.').splice(0, str.split('.').length - 1).reduce((p,c)=>p&&p[c]||null, o)[str.split('.').slice(-1)] = "some new value";
rob-gordon
1
Me gusta la idea de usar reducir, pero su lógica parece fuera de 0, undefinedy nullvalores. {a:{b:{c:0}}}vuelve en nulllugar de 0. Quizás la comprobación explícita de nulos o indefinidos aclarará estos problemas. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku
Hola @SmujMaiku, la función "listo para usar" regresa correctamente para '0', 'indefinido' y 'nulo', acabo de probar en la consola: resolvePath ({a: {b: {c: 0}}}, ' abc ', nulo) => 0; Comprueba si la clave existe en lugar del valor en sí mismo, lo que evita más de una comprobación
Adriano Spadoni
aquí defaultValue no funcionó, pero el uso de Reflect.has(o, k) ? ...(ES6 Reflect.has ) funcionó
Andre Figueiredo
setPathestablecerá el valor en la primera aparición del último selector. Por ejemplo let o = {}; setPath(o, 'a.a', 0)resultados en {a: 0}lugar de {a: {a: 0}}. Vea esta publicación para una solución.
pkfm
62

Tendría que analizar la cadena usted 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 requiere que también defina índices de matriz con notación de puntos:

var part3name1 = "part3.0.name";

Hace que el análisis sea más fácil.

MANIFESTACIÓN

Felix Kling
fuente
@Felix Kling: su solución me proporciona lo que necesito. Y muchas gracias por eso. Pero Alnitak también proporciona diferentes formas y parece funcionar tampoco. Como solo puedo elegir una respuesta, elegiré la respuesta Alnitak. No es que su solución sea mejor que tú o algo así. De todos modos, realmente aprecio tu solución y el esfuerzo que diste.
Komaruloh
1
@Komaruloh: Oh, pensé que siempre puedes votar las respuestas sobre tu propia pregunta ... de todos modos, estaba más o menos bromeando, no necesito más reputación;) ¡Feliz codificación!
Felix Kling
1
@Felix Kling: Necesitas al menos 15 reputación para votar. :) Creo que no necesitas más reputación con 69k +. Gracias
Komaruloh
@Felix FWIW: la conversión de []sintaxis a sintaxis de propiedad es bastante trivial.
Alnitak
44
Si cambia el bucle while a while (l > 0 && (obj = obj[current]) && i < l), este código también funciona para cadenas sin puntos.
Snea
39

Funciona para matrices / matrices dentro del objeto también. Defensivo contra valores no vá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>

TheZver
fuente
10
Gracias, esta es la respuesta mejor y más eficaz
Dominic
@ Sin fin, me gustaría enfatizar que la ruta debe separar los elementos con puntos. Los frenos no funcionarán. Es decir, para acceder al primer elemento de la matriz, utiliza "0.sp as".
TheZver
26

usando eval:

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

ajuste para devolver indefinido en caso de 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 al ejercer el poder de eval. Es un poco como un sable de luz, si lo enciendes hay un 90% de posibilidades de cortar una extremidad. No es para todos.

Shanimal
fuente
77
Si eval es una buena idea o no depende de dónde provienen los datos de la cadena de propiedad. Dudo que tenga alguna razón para preocuparse por los piratas informáticos que ingresan mediante una "var p = 'abc' estática; eval (p);" tipo de llamada. Es una idea perfectamente buena para eso.
James Wilkins
17

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

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

En su caso para obtener un valor de part1.namedesde someObjectacaba de hacer:

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

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

Harish Anchu
fuente
3
function deep_value (obj, ruta) {return new Function ('o', 'return o.' + ruta) (obj); }
ArcangelZith
14

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

  1. Reemplace la []sintaxis del soporte con.
  2. Split en .personaje
  3. Eliminar cadenas 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

Nick Grealy
fuente
11

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 con reducción ...

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

Plunkr

James
fuente
7

Creo que estás pidiendo esto:

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

Podrías estar preguntando por esto:

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

Ambos funcionarán


O tal vez estás pidiendo esto

var partName = "part1";
var nameStr = "name";

var part1name = someObject[partName][nameStr];

Finalmente podrías estar pidiendo esto

var partName = "part1.name";

var partBits = partName.split(".");

var part1name = someObject[partBits[0]][partBits[1]];
Hogan
fuente
Creo que OP está pidiendo la última solución. Sin embargo, las cadenas no tienen Splitmétodo, sino más bien split.
duri
Realmente estaba preguntando el último. La variable partName se llena con una cadena que indica la estructura de clave a valor. Su solución parece tener sentido. Sin embargo, es posible que deba modificar para obtener una profundidad extendida en los datos, como el nivel 4-5 y más. Y me pregunto si puedo tratar la matriz y el objeto de manera uniforme con esto.
Komaruloh
7

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

Opción 1: dividir cadena en. o [o] o 'o ", revertirlo, omitir 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 (la más rápida de todas, excepto eval): escaneo de caracteres de bajo nivel (sin expresión regular / división / etc, solo un escaneo rápido de caracteres). Nota: Este no admite comillas 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 cotizaciones, un poco más lenta pero aún más 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 (en cuanto al 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 otra cosa para estar seguro. Solo un idiota diría que nunca use "eval" en absoluto, ya que hay buenas razones para usarlo. Además, "se utiliza en el analizador JSON de Doug Crockford ". Si la entrada es segura, entonces no hay ningún problema. Use la herramienta correcta para el trabajo correcto, eso es todo.

James Wilkins
fuente
6

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

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

Acceda a objetos anidados utilizando Array Reduce

Tomemos este ejemplo de estructura

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

Para poder acceder a matrices anidadas, puede escribir su propia matriz para reducir la utilidad.

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.

También hay un tipo excelente que maneja el tipo de biblioteca mínimo que hace todo esto por usted.

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

const city = t(user, 'personalInfo.address[0].city').safeObject;

Descargo de responsabilidad: soy el autor de este paquete.

Dinesh Pandiyan
fuente
6

AngularJS

El enfoque de Speigg es muy ordenado y limpio, aunque encontré esta respuesta mientras buscaba la solución para acceder a las propiedades de alcance AngularJS $ por ruta de cadena y con una pequeña modificación hace 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 úselo en cualquier ámbito secundario como este:

$scope.resolve( 'path.to.any.object.in.scope')
nesinervink
fuente
Ver AngularJS tiene$scope.$eval otra forma de hacerlo con AngularJS.
georgeawg
3

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

Puede usar notación de puntos, corchetes, índices numéricos, propiedades de números de cadena y teclas con caracteres que no sean 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

Kyle
fuente
3
/**
 * 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")
Vitim.us
fuente
3

Función simple, que permite una ruta de cadena o 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
Ben
fuente
Si va a publicar código como respuesta, explique por qué el código responde a la pregunta.
Tieson T.
2

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

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

Prueba

Flavien Volken
fuente
Ni siquiera está comprobando si obj [clave] realmente existe. No es confiable.
Carles Alcolea
@CarlesAlcolea por defecto js tampoco verificará si la clave de un objeto existe: a.b.cgenerará una excepción si no hay ninguna propiedad ben su objeto. Si necesita algo silenciosamente descartando el camino de teclado incorrecto (que no recomiendo), aún puede reemplazar el forEach con estekeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken
Me encontré con un objeto que le faltaba una llave, mi mal :)
Carles Alcolea
1

Acabo de tener la misma pregunta recientemente y usé con éxito https://npmjs.org/package/tea-properties que también setanidaba objetos / matrices:

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
abernier
fuente
"Este módulo ha sido descontinuado. Use chaijs / pathval".
Patrick Fisher
1

Inspirado por la respuesta de @ webjay: https://stackoverflow.com/a/46008856/4110122

Hice esta función que puede usar para obtener / establecer / desarmar cualquier valor en el objeto

function Object_Manager(obj, Path, value, Action) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

Para usarlo:

 // Set
 Object_Manager(Obj,[Level1,Level2,Level3],New_Value, 'Set');

 // Get
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Get');

 // Unset
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');
Mohamad Hamouday
fuente
1

Estoy desarrollando una tienda en línea con React. Traté de cambiar los valores en el objeto de estado copiado para actualizar el estado original con él en el envío. Los ejemplos anteriores no me han funcionado, porque la mayoría de ellos mutan la estructura del objeto copiado. Encontré un ejemplo funcional de la función para acceder y cambiar los valores de las propiedades de objetos anidados profundos: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Aquí está:

const createPath = (obj, path, value = null) => {
  path = typeof path === 'string' ? path.split('.') : path;
  let current = obj;
  while (path.length > 1) {
    const [head, ...tail] = path;
    path = tail;
    if (current[head] === undefined) {
      current[head] = {};
    }
    current = current[head];
  }
  current[path[0]] = value;
  return obj;
};
Dm Mh
fuente
1

Basado en la respuesta de Alnitak .

Envolví el polyfill en un cheque y reduje la función a una reducción de cadena única.

if (Object.byPath === undefined) {
  Object.byPath = (obj, path) => path
    .replace(/\[(\w+)\]/g, '.$1')
    .replace(/^\./, '')
    .split(/\./g)
    .reduce((ref, key) => key in ref ? ref[key] : ref, obj)
}

const data = {
  foo: {
    bar: [{
      baz: 1
    }]
  }
}

console.log(Object.byPath(data, 'foo.bar[0].baz'))

Mr. Polywhirl
fuente
0

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

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

Son equivalentes al descriptor de punto de acceso y pueden variar en 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 esto aborde tu pregunta ...

EDITAR

No usaré una cadena para mantener una especie de consulta xpath para acceder a un valor de objeto. Como debe 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 está incómodo con 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 revisa en busca de errores de sintaxis, etc.

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

Eineki
fuente
Interesante. Pero en mi caso, someObject todavía se inicializa cuando asigno valor a part1name. Solo conozco la estructura. Es por eso que uso una cadena para describir la estructura. Y con la esperanza de poder usarlo para consultar mis datos de someObject. Gracias por compartir tu pensamiento. :)
Komaruloh
@Komaruloh: Creo que escribirías que el objeto NO está inicializado aún cuando creas tus variables. Por cierto, no entiendo, ¿por qué no puedes hacer la tarea en el momento adecuado?
Eineki
Lamento no mencionar que someObject aún no se ha inicializado. En cuanto a la razón, someObject se obtiene a través del servicio web. Y quiero tener una matriz de encabezado que consista en part1name, part2qty, etc. Para poder recorrer la matriz de encabezado y obtener el valor que quería en función del valor de part1name como la 'clave' / ruta a someObject.
Komaruloh
0

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. Puedes reemplazarlo por tu string_path.split(".").
  • type_of_function: 0 para acceder (no pase ningún valor a value), 0 para agregar y modificar. 1 para borrar.
ayushgp
fuente
0

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 también le permite establecer un valor!

También he creado un paquete npm y github con esto

Tamb
fuente
0

En lugar de una cadena, se puede usar una matriz que aborde 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);
Jodo
fuente
0

Basado en una respuesta anterior, he creado una función que también puede manejar 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);
}
Vincent
fuente
0

// (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"]));

Oboo Cheng
fuente
0

Trabajando con Underscore's propertyo 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...

Akash
fuente