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?


¿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.


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.

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'

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

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


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.


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/


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


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


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

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.


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: 'jack@dev.com',
    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.


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

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


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

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

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


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

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

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

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


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


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.

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


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

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

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


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

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.