Convierta cadenas de JavaScript en notación de puntos en una referencia de objeto

214

Dado un objeto JS

var obj = { a: { b: '1', c: '2' } }

y una cuerda

"a.b"

¿Cómo puedo convertir la cadena a notación de puntos para que pueda ir

var val = obj.a.b

Si la cadena fuera justa 'a', podría usarla obj[a]. Pero esto es más complejo. Me imagino que hay un método sencillo pero se escapa en la actualidad.

nevf
fuente
25
@Andrey evales malvado; no lo use
kevinji
55
FYI: Aquí hay algunas pruebas de velocidad interesantes que acabo de hacer: jsperf.com/dereference-object-property-path-from-string
James Wilkins
si perf es una consideración seria y está reutilizando mucho las mismas rutas (por ejemplo, dentro de una función de filtro de matriz), use el constructor de funciones como se describe en mi respuesta a continuación. Cuando se usa la misma ruta miles de veces, el método Function puede ser más de 10 veces más rápido que evaluar o dividir y reducir la ruta en cada desreferencia.
Kevin Crumley

Respuestas:

437

nota reciente: Aunque me siento halagado de que esta respuesta haya recibido muchos votos positivos, también estoy algo horrorizado. Si uno necesita convertir cadenas de notación de puntos como "xabc" en referencias, podría (tal vez) ser una señal de que está sucediendo algo muy malo (a menos que tal vez esté realizando una deserialización extraña).

Es decir, los novatos que encuentran su camino hacia esta respuesta deben hacerse la pregunta "¿por qué estoy haciendo esto?"

Por supuesto, generalmente está bien hacer esto si su caso de uso es pequeño y no se encontrará con problemas de rendimiento, Y no necesitará construir sobre su abstracción para complicarla más tarde. De hecho, si esto reducirá la complejidad del código y simplificará las cosas, probablemente debería seguir adelante y hacer lo que OP está pidiendo. Sin embargo, si ese no es el caso, considere si alguno de estos aplica:

caso 1 : como el método principal para trabajar con sus datos (por ejemplo, como la forma predeterminada de su aplicación de pasar objetos y desreferenciarlos). Como preguntar "¿cómo puedo buscar una función o nombre de variable de una cadena".

  • Esta es una mala práctica de programación (metaprogramación innecesaria específicamente, y viola el estilo de codificación de la función sin efectos secundarios, y tendrá resultados de rendimiento). Los novatos que se encuentran en este caso, deberían considerar trabajar con representaciones de matriz, por ejemplo ['x', 'a', 'b', 'c'], o incluso algo más directo / simple / directo si es posible: como no perder seguimiento de las referencias mismas en primer lugar (lo más ideal si es solo del lado del cliente o del lado del servidor), etc. (Una identificación única preexistente sería poco elegante para agregar, pero podría usarse si la especificación de lo contrario requiere su existencia independientemente)

caso 2 : Trabajar con datos serializados o datos que se mostrarán al usuario. Como usar una fecha como una cadena "1999-12-30" en lugar de un objeto Date (que puede causar errores de zona horaria o complejidad de serialización adicional si no se tiene cuidado). O sabes lo que estás haciendo.

  • Esto tal vez esté bien. Tenga cuidado de que no haya cadenas de puntos "." en sus fragmentos de entrada desinfectados.

Si te encuentras usando esta respuesta todo el tiempo y convirtiendo hacia adelante y hacia atrás entre una cadena y una matriz, puedes estar en el mal caso y deberías considerar una alternativa.

Aquí hay un elegante one-liner que es 10 veces más corto que las otras soluciones:

function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)

[editar] O en ECMAScript 6:

'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)

(No es que piense que eval siempre es malo como otros sugieren que es (aunque generalmente lo es), sin embargo, esas personas estarán complacidas de que este método no use eval. Lo anterior encontrará obj.a.b.etcdado objy la cadena"a.b.etc" ).

En respuesta a aquellos que todavía tienen miedo de usar a reducepesar de estar en el estándar ECMA-262 (5a edición), aquí hay una implementación recursiva de dos líneas:

function multiIndex(obj,is) {  // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
    return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) {   // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
    return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')

Dependiendo de las optimizaciones que esté haciendo el compilador JS, es posible que desee asegurarse de que las funciones anidadas no se redefinan en cada llamada a través de los métodos habituales (colocándolas en un cierre, objeto o espacio de nombres global).

editar :

Para responder una pregunta interesante en los comentarios:

¿Cómo convertirías esto en un setter también? ¿No solo devuelve los valores por ruta, sino que también los configura si se envía un nuevo valor a la función? - Swader 28 de junio a las 21:42

(nota al margen: lamentablemente no puede devolver un objeto con un Setter, ya que eso violaría la convención de llamada; el comentarista parece referirse a una función general de estilo setter con efectos secundarios como index(obj,"a.b.etc", value)hacer obj.a.b.etc = value).

El reduceestilo no es realmente adecuado para eso, pero podemos modificar la implementación recursiva:

function index(obj,is, value) {
    if (typeof is == 'string')
        return index(obj,is.split('.'), value);
    else if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

Manifestación:

> obj = {a:{b:{etc:5}}}

> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc'])   #works with both strings and lists
5

> index(obj,'a.b.etc', 123)    #setter-mode - third argument (possibly poor form)
123

> index(obj,'a.b.etc')
123

... aunque personalmente recomendaría hacer una función separada setIndex(...). Me gustaría terminar con una nota al margen de que el autor original de la pregunta podría (¿debería?) Estar trabajando con conjuntos de índices (que pueden obtener .split), en lugar de cadenas; aunque generalmente no hay nada malo con una función de conveniencia.


Un comentarista preguntó:

¿Qué pasa con las matrices? algo así como "ab [4] .cd [1] [2] [3]"? –AlexS

Javascript es un lenguaje muy extraño; en general, los objetos solo pueden tener cadenas como sus claves de propiedad, por lo que, por ejemplo, si xfuera un objeto genérico como x={}, entonces x[1]se convertiría en x["1"]... usted lo leyó bien ... sí ...

Las matrices de Javascript (que son en sí mismas instancias de Object) fomentan específicamente las claves enteras, aunque podría hacer algo como x=[]; x["puppy"]=5; .

Pero en general (y hay excepciones), x["somestring"]===x.somestring(cuando está permitido; no se puede hacer x.123).

(Tenga en cuenta que cualquier compilador JS que esté usando podría elegir, tal vez, compilarlos en representaciones más sensatas si puede probar que no violaría la especificación).

Entonces, la respuesta a su pregunta dependerá de si está asumiendo que esos objetos solo aceptan enteros (debido a una restricción en su dominio problemático), o no. Asumamos que no. Entonces, una expresión válida es una concatenación de un identificador base más algunos .identifiers más algunos ["stringindex"]s

Esto sería equivalente a a["b"][4]["c"]["d"][1][2][3], aunque probablemente también deberíamos apoyarlo a.b["c\"validjsstringliteral"][3]. Tendría que consultar la sección de gramática ecmascript en literales de cadena para ver cómo analizar un literal de cadena válido. Técnicamente, también querría verificar (a diferencia de mi primera respuesta) que aes un identificador de JavaScript válido .

Una respuesta simple a su pregunta, sin embargo, si sus cadenas no contienen comas o soportes , sería apenas sea para que coincida con la longitud 1+ secuencias de caracteres no en el conjunto ,o [o ]:

> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^  ^ ^^^ ^  ^   ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]

Si sus cadenas no contienen caracteres de escape o "caracteres , y debido a que IdentifierNames es un sublenguaje de StringLiterals (creo que ???), primero puede convertir sus puntos a []:

> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g; 
      match=matcher.exec(demoString); ) {
  R.push(Array.from(match).slice(1).filter(x=>x!==undefined)[0]);
  // extremely bad code because js regexes are weird, don't use this
}
> R

["abc", "4", "c", "def", "1", "2", "gh"]

Por supuesto, siempre tenga cuidado y nunca confíe en sus datos. Algunas formas malas de hacer esto que podrían funcionar para algunos casos de uso también incluyen:

// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.: 
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3"  //use code from before

Edición especial 2018:

Hagamos un círculo completo y hagamos la solución más ineficiente y terriblemente sobreprogramada que podamos encontrar ... en interés del servicio de pureza sintáctica . ¡Con objetos Proxy ES6! ... Definamos también algunas propiedades que (aunque son buenas y maravillosas) pueden romper bibliotecas mal escritas. Tal vez debería desconfiar de usar esto si le preocupa el rendimiento, la cordura (la suya o la de los demás), su trabajo, etc.

// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
    Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub

// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization

// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
    get: function(obj,key, proxy) {
        return key.split('.').reduce((o,i)=>o[i], obj);
    },
    set: function(obj,key,value, proxy) {
        var keys = key.split('.');
        var beforeLast = keys.slice(0,-1).reduce((o,i)=>o[i], obj);
        beforeLast[keys[-1]] = value;
    },
    has: function(obj,key) {
        //etc
    }
};
function hyperIndexOf(target) {
    return new Proxy(target, hyperIndexProxyHandler);
}

Manifestación:

var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));

var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));

console.log("(behind the scenes) objHyper is:", objHyper);

if (!({}).H)
    Object.defineProperties(Object.prototype, {
        H: {
            get: function() {
                return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
            }
        }
    });

console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);

Salida:

obj es: {"a": {"b": {"c": 1, "d": 2}}}

(proxy override get) objHyper ['abc'] es: 1

(conjunto de anulación de proxy) objHyper ['abc'] = 3, ahora obj es: {"a": {"b": {"c": 3, "d": 2}}}

(detrás de escena) objHyper es: Proxy {a: {…}}

(acceso directo) obj.H ['abc'] = 4

(acceso directo) obj.H ['abc'] es obj ['a'] ['b'] ['c'] es: 4

idea ineficiente: puede modificar lo anterior para despachar en función del argumento de entrada; use el .match(/[^\]\[.]+/g)método para admitir obj['keys'].like[3]['this'], o si instanceof Array, simplemente acepte una matriz como entrada como keys = ['a','b','c']; obj.H[keys].


Por sugerencia de que tal vez desee manejar índices indefinidos de una manera 'más suave' al estilo NaN (por ejemplo, index({a:{b:{c:...}}}, 'a.x.c')devolver Indefinido en lugar de no detectado TypeError) ...:

1) Esto tiene sentido desde la perspectiva de "deberíamos regresar indefinido en lugar de arrojar un error" en la situación del índice unidimensional ({}) ['eg'] == undefined, entonces "deberíamos regresar indefinido en lugar de arrojar un error "en la situación N-dimensional.

2) Esto no tiene sentido desde la perspectiva que estamos haciendo x['a']['x']['c'], lo que fallaría con un TypeError en el ejemplo anterior.

Dicho esto, haría que esto funcionara reemplazando su función reductora con:

(o,i)=>o===undefined?undefined:o[i]O (o,i)=>(o||{})[i].

(Puede hacer esto más eficiente usando un bucle for y rompiendo / regresando cada vez que el resultado secundario en el que se indexará a continuación no esté definido, o usando un try-catch si espera que tales fallas sean lo suficientemente raras).

ninjagecko
fuente
44
reduceno es compatible con todos los navegadores utilizados actualmente.
Ricardo Tomasi
14
@ Ricardo: Array.reducees parte del estándar ECMA-262. Si realmente desea admitir navegadores obsoletos, puede definir Array.prototype.reducela implementación de muestra dada en algún lugar (por ejemplo, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko
2
Sí, pero es bastante fácil poner las dos líneas en una función. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf
3
Me encanta este elegante ejemplo, gracias ninjagecko. Lo extendí para manejar la notación de estilo de matriz, así como las cadenas vacías. Vea mi ejemplo aquí: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD
2
@ninjagecko, ¿cómo convertirías esto en un setter también? ¿No solo devuelve los valores por ruta, sino que también los configura si se envía un nuevo valor a la función?
Swader
64

Si puede usar lodash , hay una función que hace exactamente eso:

_.get (objeto, ruta, [valor predeterminado])

var val = _.get(obj, "a.b");
Petar Ivanov
fuente
44
Nota: _.get(object, path)no se rompe si no se encontró una ruta. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)hace. Para mi caso específico, no para cada caso, exactamente lo que necesitaba. ¡Gracias!
Sr. B.
1
@ Mr.B. La última versión de Lodash tiene un tercer argumento opcional para defaultValue. El _.get()método devuelve el valor predeterminado si se _.get()resuelve undefined, así que configúrelo en lo que desee y observe el valor que configuró.
impulsado por vapor el
77
Para cualquiera que se pregunte, también es compatible _.set(object, path, value).
Jeffrey Roosendaal
19

Un ejemplo un poco más complicado con la recursividad.

function recompose(obj,string){
    var parts = string.split('.');
    var newObj = obj[parts[0]];
    if(parts[1]){
        parts.splice(0,1);
        var newString = parts.join('.');
        return recompose(newObj,newString);
    }
    return newObj;
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

alert(recompose(obj,'a.d.a.b')); //blah
joekarl
fuente
19

también puedes usar lodash.get

Simplemente instale este paquete (npm i --save lodash.get) y luego utilícelo así:

const get = require('lodash.get');

const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };

console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints  123

//You can also update values
get(myObj, 'user').firstName = John;
DarkCrazy
fuente
10

Si espera desreferenciar la misma ruta muchas veces, crear una función para cada ruta de notación de puntos en realidad tiene el mejor rendimiento con diferencia (expandiendo las pruebas de rendimiento a las que James Wilkins se vincula en los comentarios anteriores).

var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);

El uso del constructor de funciones tiene algunos de los mismos inconvenientes que eval () en términos de seguridad y el peor de los casos, pero IMO es una herramienta mal utilizada para casos en los que necesita una combinación de dinamismo extremo y alto rendimiento. Utilizo esta metodología para construir funciones de filtro de matriz y llamarlas dentro de un bucle de resumen AngularJS. Mis perfiles muestran consistentemente el paso array.filter () que toma menos de 1 ms para desreferenciar y filtrar alrededor de 2000 objetos complejos, utilizando rutas definidas dinámicamente de 3 a 4 niveles de profundidad.

Una metodología similar podría usarse para crear funciones de establecimiento, por supuesto:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Kevin Crumley
fuente
1
Si necesita desreferenciar las mismas rutas con mucho tiempo de diferencia, el enlace de jsperf.com anterior muestra un ejemplo de cómo guardar y buscar la función más adelante. El acto de llamar al constructor de la función es bastante lento, por lo que el código de alto rendimiento debe memorizar los resultados para evitar repetirlo si es posible.
Kevin Crumley
7

Sugiero dividir el camino e iterarlo y reducir el objeto que tiene. Esta propuesta funciona con un valor predeterminado para las propiedades faltantes.

const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);

console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));

Nina Scholz
fuente
6

Tenga en cuenta que si ya está usando Lodash , puede usar las funciones propertyo get:

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

El subrayado también tiene una propertyfunción, pero no admite la notación de puntos.

Tamlyn
fuente
1
Parece que el subrayado no admite la notación de puntos. Respuesta actualizada
Tamlyn
5

Otras propuestas son un poco crípticas, así que pensé que contribuiría:

Object.prop = function(obj, prop, val){
    var props = prop.split('.')
      , final = props.pop(), p 
    while(p = props.shift()){
        if (typeof obj[p] === 'undefined')
            return undefined;
        obj = obj[p]
    }
    return val ? (obj[final] = val) : obj[final]
}

var obj = { a: { b: '1', c: '2' } }

// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }
Ricardo Tomasi
fuente
5

Puede usar la biblioteca disponible en npm, lo que simplifica este proceso. https://www.npmjs.com/package/dot-object

 var dot = require('dot-object');

var obj = {
 some: {
   nested: {
     value: 'Hi there!'
   }
 }
};

var val = dot.pick('some.nested.value', obj);
console.log(val);

// Result: Hi there!
Jeremias Santos
fuente
1
Gracias por informarnos de esto. Se ve muy útil.
nevf
Y es por eso que cada proyecto inyecta 10000 dependencias :-).
Marvin
4
var a = { b: { c: 9 } };

function value(layer, path, value) {
    var i = 0,
        path = path.split('.');

    for (; i < path.length; i++)
        if (value != null && i + 1 === path.length)
            layer[path[i]] = value;
        layer = layer[path[i]];

    return layer;
};

value(a, 'b.c'); // 9

value(a, 'b.c', 4);

value(a, 'b.c'); // 4

Esto es mucho código en comparación con la forma mucho más simple evalde hacerlo, pero como dice Simon Willison, nunca debes usar eval .

Además, JSFiddle .

McKayla
fuente
Genial, esto funciona muy bien y es más corto que el CD Sanchez. Gracias.
nevf
valor (a, 'bbbbb') no devuelve indefinido
frumbert
4

He extendido la respuesta elegante de ninjagecko para que la función maneje las referencias de estilo punteadas y / o de matriz, y para que una cadena vacía haga que se devuelva el objeto padre.

Aqui tienes:

string_to_ref = function (object, reference) {
    function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
    function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
    return !reference ? object : reference.split('.').reduce(dot_deref, object);
};

Vea mi ejemplo de trabajo jsFiddle aquí: http://jsfiddle.net/sc0ttyd/q7zyd/

Sc0ttyD
fuente
Muy buena solución. Solo hay un problema, se supone que la []notación es siempre para matrices. también puede haber claves de objeto representadas de esa manera, por ejemplo. obj['some-problem/name'].list[1]Para solucionar esto, tuve que actualizar una arr_dereffunción como esta javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
Serkan Yersen,
1
Aunque, hoy en día, no haría esto. Usaría Lodash: lodash.com/docs#get
Sc0ttyD
2
var find = function(root, path) {
  var segments = path.split('.'),
      cursor = root,
      target;

  for (var i = 0; i < segments.length; ++i) {
   target = cursor[segments[i]];
   if (typeof target == "undefined") return void 0;
   cursor = target;
  }

  return cursor;
};

var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1

var set = function (root, path, value) {
   var segments = path.split('.'),
       cursor = root,
       target;

   for (var i = 0; i < segments.length - 1; ++i) {
      cursor = cursor[segments[i]] || { };
   }

   cursor[segments[segments.length - 1]] = value;
};

set(obj, "a.k", function () { console.log("hello world"); });

find(obj, "a.k")(); // hello world
Cristian Sanchez
fuente
Gracias por todas las respuestas rápidas. No me gustan las soluciones eval (). Esta y las publicaciones similares se ven mejor. Sin embargo, todavía tengo un problema. Estoy tratando de establecer el valor obj.ab = nuevo valor. Para ser precisos, el valor de b es una función, así que necesito usar obj.ab (new_value). Se llama a la función pero no se establece el valor. Creo que es un problema de alcance pero todavía estoy cavando. Me doy cuenta de que esto está fuera del alcance de la pregunta original. Mi código está usando Knockout.js yb es un ko.observable.
nevf
@nevf: agregué una segunda función que creo que hace lo que quieres. Puede personalizarlo a su gusto según el comportamiento que desee (por ejemplo, ¿debería crear los objetos si no existen ?, etc.).
Cristian Sanchez
@nevf Pero el mío lo hace con una función. ; D
McKayla
gracias por la actualización que pude usar. @tylermwashburn, y gracias por su implementación más corta, que también funciona. Que tengan un gran w / e todos.
nevf
2

Puede obtener el valor de un miembro de objeto mediante notación de puntos con una sola línea de código:

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

En tu caso:

var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);

Para hacerlo simple, puede escribir una función como esta:

function objGet(obj, path){
    return new Function('_', 'return _.' + path)(obj);
}

Explicación:

El constructor de funciones crea un nuevo objeto de función. En JavaScript, cada función es en realidad un objeto Función. La sintaxis para crear una función explícitamente con el constructor de funciones es:

new Function ([arg1[, arg2[, ...argN]],] functionBody)

donde arguments(arg1 to argN)debe ser una cadena que corresponda a un identificador javaScript válido yfunctionBody es una cadena que contiene las declaraciones javaScript que comprenden la definición de la función.

En nuestro caso, aprovechamos el cuerpo de la función de cadena para recuperar el miembro del objeto con notación de puntos.

Espero eso ayude.

Harish Anchu
fuente
¿Puedes explicar exactamente qué está haciendo esto? ¿Y cómo pasarías 'ab', etc. como un parámetro de función?
nevf
1
Le di a esto un +1 pero JSLint advierte que "el constructor de funciones es una forma de evaluación" .
gabe
2

Respuesta GET / SET que también funciona en react native (no se puede asignar a Object.prototypeactualmente):

Object.defineProperty(Object.prototype, 'getNestedProp', {
    value: function(desc) {
        var obj = this;
        var arr = desc.split(".");
        while(arr.length && (obj = obj[arr.shift()]));
        return obj;
    },
    enumerable: false
});

Object.defineProperty(Object.prototype, 'setNestedProp', {
    value: function(desc, value) {
        var obj = this;
        var arr = desc.split(".");
        var last = arr.pop();
        while(arr.length && (obj = obj[arr.shift()]));
        obj[last] = value;
    },
    enumerable: false
});

Uso:

var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };

a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo
Alexander Danilov
fuente
1

Copié lo siguiente de la respuesta de Ricardo Tomasi y modifiqué para crear también subobjetos que aún no existen según sea necesario. Es un poco menos eficiente (másif s y creación de objetos vacíos), pero debería ser bastante bueno.

Además, nos permitirá hacer Object.prop(obj, 'a.b', false)donde antes no podíamos. Desafortunadamente, todavía no nos deja asignar undefined... No estoy seguro de cómo hacerlo todavía.

/**
 * Object.prop()
 *
 * Allows dot-notation access to object properties for both getting and setting.
 *
 * @param {Object} obj    The object we're getting from or setting
 * @param {string} prop   The dot-notated string defining the property location
 * @param {mixed}  val    For setting only; the value to set
 */
 Object.prop = function(obj, prop, val){
   var props = prop.split('.'),
       final = props.pop(),
       p;

   for (var i = 0; i < props.length; i++) {
     p = props[i];
     if (typeof obj[p] === 'undefined') {
       // If we're setting
       if (typeof val !== 'undefined') {
         // If we're not at the end of the props, keep adding new empty objects
         if (i != props.length)
           obj[p] = {};
       }
       else
         return undefined;
     }
     obj = obj[p]
   }
   return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
 }
Chris V.
fuente
1

Si desea convertir cualquier objeto que contenga claves de notación de puntos en una versión ordenada de esas claves, puede usar esto.


Esto convertirá algo como

{
  name: 'Andy',
  brothers.0: 'Bob'
  brothers.1: 'Steve'
  brothers.2: 'Jack'
  sisters.0: 'Sally'
}

a

{
  name: 'Andy',
  brothers: ['Bob', 'Steve', 'Jack']
  sisters: ['Sally']
}

convertDotNotationToArray(objectWithDotNotation) {

    Object.entries(objectWithDotNotation).forEach(([key, val]) => {

      // Is the key of dot notation 
      if (key.includes('.')) {
        const [name, index] = key.split('.');

        // If you have not created an array version, create one 
        if (!objectWithDotNotation[name]) {
          objectWithDotNotation[name] = new Array();
        }

        // Save the value in the newly created array at the specific index 
        objectWithDotNotation[name][index] = val;
        // Delete the current dot notation key val
        delete objectWithDotNotation[key];
      }
    });

}
Matthew Mullin
fuente
No procesó los valores con "brothers.0.one" como JSON.
Karthick
¿Tiene alguna sugerencia para procesar JSON más complejos con la colección de matriz?
Karthick
0

Aquí está mi código sin usar eval. También es fácil de entender.

function value(obj, props) {
  if (!props) return obj;
  var propsArr = props.split('.');
  var prop = propsArr.splice(0, 1);
  return value(obj[prop], propsArr.join('.'));
}

var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

console.log(value(obj, 'a.d.a.b')); //returns blah
Alvin Magalona
fuente
0

Sí, se le preguntó hace 4 años y sí, extender prototipos base no suele ser una buena idea, pero si mantiene todas las extensiones en un solo lugar, podrían ser útiles.
Entonces, aquí está mi manera de hacer esto.

   Object.defineProperty(Object.prototype, "getNestedProperty", {
    value     : function (propertyName) {
        var result = this;
        var arr = propertyName.split(".");

        while (arr.length && result) {
            result = result[arr.shift()];
        }

        return result;
    },
    enumerable: false
});

Ahora podrá obtener propiedades anidadas en todas partes sin importar el módulo con la función o la función copiar / pegar.

UPD Ejemplo:

{a:{b:11}}.getNestedProperty('a.b'); //returns 11

UPD 2. La siguiente extensión rompe la mangosta en mi proyecto. También he leído que podría romper jquery. Entonces, nunca lo hagas de la siguiente manera

 Object.prototype.getNestedProperty = function (propertyName) {
    var result = this;
    var arr = propertyName.split(".");

    while (arr.length && result) {
        result = result[arr.shift()];
    }

    return result;
};
Michael Kapustey
fuente
0

Aquí está mi implementación

Implementación 1

Object.prototype.access = function() {
    var ele = this[arguments[0]];
    if(arguments.length === 1) return ele;
    return ele.access.apply(ele, [].slice.call(arguments, 1));
}

Implementación 2 (usando la reducción de matriz en lugar de la división)

Object.prototype.access = function() {
    var self = this;
    return [].reduce.call(arguments,function(prev,cur) {
        return prev[cur];
    }, self);
}

Ejemplos:

var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};

myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11

También puede manejar objetos dentro de matrices como para

var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'
Xeltor
fuente
0

Esta es mi solución extendida propuesta por: ninjagecko

Para mí, la notación de cadena simple no era suficiente, por lo que la siguiente versión admite cosas como:

index (obj, 'data.accounts [0] .address [0] .postcode');

/**
 * Get object by index
 * @supported
 * - arrays supported
 * - array indexes supported
 * @not-supported
 * - multiple arrays
 * @issues:
 *  index(myAccount, 'accounts[0].address[0].id') - works fine
 *  index(myAccount, 'accounts[].address[0].id') - doesnt work
 * @Example:
 * index(obj, 'data.accounts[].id') => returns array of id's
 * index(obj, 'data.accounts[0].id') => returns id of 0 element from array
 * index(obj, 'data.accounts[0].addresses.list[0].id') => error
 * @param obj
 * @param path
 * @returns {any}
 */
var index = function(obj, path, isArray?, arrIndex?){

    // is an array
    if(typeof isArray === 'undefined') isArray = false;
    // array index,
    // if null, will take all indexes
    if(typeof arrIndex === 'undefined') arrIndex = null;

    var _arrIndex = null;

    var reduceArrayTag = function(i, subArrIndex){
        return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
            var tmp = i.match(/(\[)([\d]{0,})(\])/);
            isArray = true;
            if(subArrIndex){
                _arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }else{
                arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }
            return '';
        });
    }

    function byIndex(obj, i) {
        // if is an array
        if(isArray){
            isArray = false;
            i = reduceArrayTag(i, true);
            // if array index is null,
            // return an array of with values from every index
            if(!arrIndex){
                var arrValues = [];
                _.forEach(obj, (el) => {
                    arrValues.push(index(el, i, isArray, arrIndex));
                })
                return arrValues;
            }
            // if array index is specified
            var value = obj[arrIndex][i];
            if(isArray){
                arrIndex = _arrIndex;
            }else{
                arrIndex = null;
            }
            return value;
        }else{
            // remove [] from notation,
            // if [] has been removed, check the index of array
            i = reduceArrayTag(i, false);
            return obj[i]
        }
    }

    // reduce with byIndex method
    return path.split('.').reduce(byIndex, obj)
}
Yabree
fuente
0

A riesgo de vencer a un caballo muerto ... Esto me resulta muy útil al atravesar objetos anidados para hacer referencia a dónde se encuentra con respecto al objeto base o un objeto similar con la misma estructura. Para ese fin, esto es útil con una función transversal de objeto anidado. Tenga en cuenta que he usado una matriz para mantener la ruta. Sería trivial modificar esto para usar una ruta de cadena o una matriz. También tenga en cuenta que puede asignar "indefinido" al valor, a diferencia de algunas de las otras implementaciones.

/*
 * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
 * on each. The path is an array of the keys required to get to curObject from
 * baseObject using objectPath(). If the call to fn() returns falsey, objects below
 * curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
 * The third and fourth arguments are only used by recursion.
 */
function objectTraverse (o, fn, base, path) {
    path = path || [];
    base = base || o;
    Object.keys(o).forEach(function (key) {
        if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
            path.push(key);
            objectTraverse(o[key], fn, base, path);
            path.pop();
        }
    });
}

/*
 * Get/set a nested key in an object. Path is an array of the keys to reference each level
 * of nesting. If value is provided, the nested key is set.
 * The value of the nested key is returned.
 */
function objectPath (o, path, value) {
    var last = path.pop();

    while (path.length && o) {
        o = o[path.shift()];
    }
    if (arguments.length < 3) {
        return (o? o[last] : o);
    }
    return (o[last] = value);
}

srkleiman
fuente
0

Usé este código en mi proyecto

const getValue = (obj, arrPath) => (
  arrPath.reduce((x, y) => {
    if (y in x) return x[y]
    return {}
  }, obj)
)

Uso:

const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104
Prathak Paisanwatcharakit
fuente
0

Pocos años después, encontré esto que maneja el alcance y la matriz. p.eja['b']["c"].d.etc

function getScopedObj(scope, str) {
  let obj=scope, arr;

  try {
    arr = str.split(/[\[\]\.]/) // split by [,],.
      .filter(el => el)             // filter out empty one
      .map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
    arr.forEach(el => obj = obj[el])
  } catch(e) {
    obj = undefined;
  }

  return obj;
}

window.a = {b: {c: {d: {etc: 'success'}}}}

getScopedObj(window, `a.b.c.d.etc`)             // success
getScopedObj(window, `a['b']["c"].d.etc`)       // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined
allenhwkim
fuente
-1

No está claro cuál es tu pregunta. Dado su objeto, obj.a.ble daría "2" tal como es. Si desea manipular la cadena para utilizar corchetes, puede hacer esto:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Mark Eirich
fuente
1
Esto no funciona a.b.cy realmente no logra lo que quieren ... Quieren el valor, no un evalcamino.
McKayla
Ahora lo arreglé para que funcione a.b.c, pero tienes razón, aparentemente quería obtener / establecer el valor de la propiedad en obj.a.b. La pregunta fue confusa para mí, ya que dijo que quería "convertir la cadena" ...
Mark Eirich
Buen trabajo. :) Fue un poco vago. Sin embargo, hiciste un buen trabajo de conversión.
McKayla
-1

aquí están mis 10 centavos por caja, la función de abajo se obtendrá / establecerá en función de la ruta proporcionada, .. seguro de que puede mejorarla, elimine || y reemplácelo con Object.hasOwnPropertysi le importan los valores falsos por error,

Lo probé con a.b.cy ab2.c{a:{b:[0,1,{c:7}]}} y funciona tanto para configurar como para obtener :).

cheerz

function helper(obj, path, setValue){
  const l = String(path).split('.');
  return l.reduce((o,i, idx)=>{
   if( l.length-idx===1)  { o[i] = setValue || o[i];return setValue ? obj : o[i];}
  o[i] = o[i] || {};
   return o[i];
  }, x)
}
Zalaboza
fuente