Converter string JavaScript em notação de ponto em uma referência de objeto

214

Dado um objeto JS

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

e uma corda

"a.b"

como posso converter a string em notação de ponto para que eu possa ir

var val = obj.a.b

Se a string fosse justa 'a', eu poderia usar obj[a]. Mas isso é mais complexo. Imagino que exista algum método direto, mas ele escapa no momento.

nevf
fonte
25
@Andrey evalé mau; não usá-lo
kevinji
5
FYI: Aqui estão alguns testes de velocidade interessantes eu fiz: jsperf.com/dereference-object-property-path-from-string
James Wilkins
se perf for uma consideração séria e você estiver reutilizando muito os mesmos caminhos (por exemplo, dentro de uma função de filtro de matriz), use o construtor Function, conforme descrito na minha resposta abaixo. Quando o mesmo caminho é usado milhares de vezes, o método Function pode ser 10 vezes mais rápido que avaliar ou dividir e reduzir o caminho em todas as desreferências.
precisa
related:
Maneira

Respostas:

437

nota recente: Embora eu esteja lisonjeado por esta resposta ter recebido muitos votos positivos, também estou um pouco horrorizado. Se for necessário converter seqüências de notação de ponto como "xabc" em referências, pode (talvez) ser um sinal de que algo está errado (a menos que você esteja executando uma desserialização estranha).

Ou seja, os novatos que encontrarem o caminho para essa resposta devem se perguntar "por que estou fazendo isso?"

É claro que geralmente é bom fazer isso se o seu caso de uso for pequeno e você não tiver problemas de desempenho, E você não precisará desenvolver sua abstração para torná-la mais complicada posteriormente. De fato, se isso reduzir a complexidade do código e simplificar as coisas, você provavelmente deve seguir em frente e fazer o que o OP está pedindo. No entanto, se esse não for o caso, considere se alguma delas se aplica:

caso 1 : como o método principal de trabalhar com seus dados (por exemplo, como a forma padrão do seu aplicativo de passar objetos e retirá-los de referência). Como perguntar "como posso procurar uma função ou nome de variável a partir de uma string".

  • Essa é uma prática ruim de programação (metaprogramação desnecessária especificamente, e meio que viola o estilo de codificação sem efeito colateral da função e terá resultados de desempenho). Os novatos que se encontram nesse caso devem considerar trabalhar com representações de array, por exemplo, ['x', 'a', 'b', 'c'] ou até algo mais direto / simples / direto, se possível: como não perder rastrear as próprias referências em primeiro lugar (ideal se for apenas do lado do cliente ou do lado do servidor), etc. (um ID exclusivo pré-existente seria deselegante de adicionar, mas poderia ser usado se a especificação exigir existência independentemente.)

caso 2 : trabalhando com dados serializados, ou dados que serão exibidos para o usuário. Como usar uma data como uma string "1999-12-30" em vez de um objeto Date (que pode causar erros de fuso horário ou adicionar complexidade de serialização, se não for cuidadoso). Ou você sabe o que está fazendo.

  • Talvez esteja bom. Cuidado para não haver seqüências de pontos "." em seus fragmentos de entrada higienizados.

Se você se encontra usando essa resposta o tempo todo e convertendo entre string e array, pode estar em um caso ruim e deve considerar uma alternativa.

Aqui está uma linha única elegante que é 10x mais curta que as outras soluções:

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

[editar] Ou no ECMAScript 6:

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

(Não que eu ache o eval sempre ruim, como outros sugerem), mas essas pessoas ficarão satisfeitas com o fato de esse método não usar o eval. O item acima encontrará obj.a.b.etcdados obje a string "a.b.etc".)

Em resposta àqueles que ainda têm medo de usar, reduceapesar de estar no padrão ECMA-262 (5ª edição), aqui está uma implementação recursiva de duas linhas:

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

Dependendo das otimizações que o compilador JS está executando, convém garantir que as funções aninhadas não sejam redefinidas em todas as chamadas pelos métodos usuais (colocando-as em um fechamento, objeto ou espaço de nome global).

editar :

Para responder a uma pergunta interessante nos comentários:

como você transformaria isso em um setter também? Não apenas retornando os valores por caminho, mas também definindo-os se um novo valor for enviado para a função? - Swader 28/06 às 21:42

(nota de rodapé: infelizmente não é possível retornar um objeto com um setter, pois isso violaria a convenção de chamada; o comentarista parece estar se referindo a uma função geral do estilo setter com efeitos colaterais como index(obj,"a.b.etc", value)fazer obj.a.b.etc = value.)

O reduceestilo não é realmente adequado para isso, mas podemos modificar a implementação 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);
}

Demo:

> 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

... embora pessoalmente eu recomendo fazer uma função separada setIndex(...). Gostaria de terminar com uma nota lateral que o poser original da pergunta poderia (deveria?) Estar trabalhando com matrizes de índices (dos quais eles podem obter .split), em vez de seqüências de caracteres; embora geralmente não haja nada de errado com uma função de conveniência.


Um comentarista perguntou:

e matrizes? algo como "ab [4] .cd [1] [2] [3]"? –AlexS

Javascript é uma linguagem muito estranha; em geral, os objetos só podem ter cadeias de caracteres como suas chaves de propriedade, por exemplo, se xfosse um objeto genérico como x={}, então x[1]se tornaria x["1"]... você leu certo ... sim ...

Matrizes Javascript (que são instâncias do Object) especificamente incentivam chaves inteiras, mesmo que você possa fazer algo assim x=[]; x["puppy"]=5;.

Mas em geral (e há exceções), x["somestring"]===x.somestring(quando é permitido; você não pode fazer x.123).

(Lembre-se de que qualquer compilador JS que você estiver usando possa escolher, talvez, compilá-los em representações mais saudáveis, se puder provar que não violaria as especificações.)

Portanto, a resposta para sua pergunta depende se você está assumindo que esses objetos aceitam apenas números inteiros (devido a uma restrição no domínio do problema) ou não. Vamos supor que não. Então uma expressão válida é uma concatenação de um identificador de base mais alguns .identifiers mais alguns ["stringindex"]s

Isso seria equivalente a a["b"][4]["c"]["d"][1][2][3], embora provavelmente devêssemos também apoiar a.b["c\"validjsstringliteral"][3]. Você precisaria verificar a seção de gramática de ecmascript em literais de string para ver como analisar um literal de string válido. Tecnicamente, você também gostaria de verificar (ao contrário da minha primeira resposta) que aé um identificador javascript válido .

Uma resposta simples para sua pergunta, porém, se suas seqüências de caracteres não contiverem vírgulas ou colchetes , seria apenas corresponder ao comprimento 1 ou mais seqüências de caracteres que não estão no conjunto ,ou [ou ]:

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

Se suas strings não contiverem caracteres de escape ou "caracteres , e porque IdentifierNames são uma sub-linguagem de StringLiterals (acho ???), você pode primeiro converter seus pontos em []:

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

Obviamente, sempre tenha cuidado e nunca confie em seus dados. Algumas maneiras ruins de fazer isso que podem funcionar em alguns casos de uso também incluem:

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

Edição especial de 2018:

Vamos para um círculo completo e fazer a solução mais ineficiente, horrivelmente-overmetaprogrammed podemos chegar a ... no interesse da sintática pureza hamfistery. Com os objetos ES6 Proxy! ... Vamos também definir algumas propriedades que (imho são boas e maravilhosas, mas) podem quebrar bibliotecas gravadas incorretamente. Talvez você deva ter cuidado ao usar isso se se preocupa com desempenho, sanidade (sua ou de outras pessoas), seu trabalho, 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);
}

Demo:

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

Resultado:

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

(substituição de proxy get) objHyper ['abc'] é: 1

(conjunto de substituições de proxy) objHyper ['abc'] = 3, agora obj é: {"a": {"b": {"c": 3, "d": 2}}}

(nos bastidores) objHyper é: Proxy {a: {…}}

(atalho) obj.H ['abc'] = 4

(atalho) obj.H ['abc'] é obj ['a'] ['b'] ['c'] é: 4

idéia ineficiente: você pode modificar o item acima para enviar com base no argumento de entrada; use o .match(/[^\]\[.]+/g)método para dar suporte obj['keys'].like[3]['this']ou instanceof Array, se aceitar, apenas uma matriz como entrada keys = ['a','b','c']; obj.H[keys].


Por sugestão de que talvez você queira manipular índices indefinidos de uma maneira 'suave' no estilo NaN (por exemplo, index({a:{b:{c:...}}}, 'a.x.c')retornar indefinido em vez de TypeError não detectado) ...:

1) Isso faz sentido da perspectiva de "devemos retornar indefinidos em vez de gerar um erro" na situação do índice unidimensional ({}) ['eg'] == indefinido, portanto "devemos retornar indefinidos em vez de lançar um erro "na situação N-dimensional.

2) Isso não faz sentido da perspectiva que estamos fazendo x['a']['x']['c'], o que falharia com um TypeError no exemplo acima.

Dito isso, você faria esse trabalho substituindo sua função de redução por:

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

(Você pode tornar isso mais eficiente usando um loop for e interrompendo / retornando sempre que o sub-resultado no qual você indexar o próximo for indefinido ou usando um try-catch se você espera que essas falhas sejam suficientemente raras.)

ninjagecko
fonte
4
reducenão é suportado em todos os navegadores usados ​​atualmente.
Ricardo Tomasi
14
@ Ricardo: Array.reducefaz parte do padrão ECMA-262. Se você realmente deseja oferecer suporte a navegadores desatualizados, pode definir Array.prototype.reducea implementação de amostra fornecida em algum lugar (por exemplo, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
Ninjagecko
2
Sim, mas é fácil colocar as duas linhas em uma função. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
Nevf
3
Eu amo este exemplo elegante, obrigado ninjagecko. Eu o estendi para lidar com notação de estilo de matriz, bem como cadeias de caracteres vazias - veja meu exemplo aqui: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD
2
@ninjagecko como você transforma isso em um setter também? Não apenas retornando os valores por caminho, mas também definindo-os se um novo valor for enviado para a função?
Swader
64

Se você pode usar o lodash , existe uma função que faz exatamente isso:

_.get (objeto, caminho, [defaultValue])

var val = _.get(obj, "a.b");
Petar Ivanov
fonte
4
Nota: _.get(object, path)não quebra se um caminho não foi encontrado. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)faz. Para o meu caso específico - não para todos os casos - exatamente o que eu precisava. Obrigado!
Sr. B.
1
@ Mr.B. a versão mais recente do Lodash possui um terceiro argumento opcional defaultValue. O _.get()método retorna o valor padrão se for _.get()resolvido undefined, então defina-o como desejar e observe o valor definido.
usar o seguinte
7
Para quem se pergunta, também suporta _.set(object, path, value).
Jeffrey Roosendaal
19

Um exemplo um pouco mais envolvido com recursão.

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
fonte
19

você também pode usar o lodash.get

Você acabou de instalar este pacote (npm i --save lodash.get) e depois o usa assim:

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
fonte
10

Se você espera desreferenciar o mesmo caminho muitas vezes, a criação de uma função para cada caminho de notação de ponto realmente tem o melhor desempenho (de longe, expandindo os testes de desempenho aos quais James Wilkins vinculou nos comentários acima).

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

O uso do construtor Function apresenta as mesmas desvantagens que eval () em termos de segurança e pior desempenho, mas o IMO é uma ferramenta pouco utilizada para casos em que você precisa de uma combinação de extremo dinamismo e alto desempenho. Eu uso essa metodologia para criar funções de filtro de matriz e chamá-las dentro de um loop de resumo do AngularJS. Meus perfis mostram consistentemente a etapa array.filter () que leva menos de 1ms para desreferenciar e filtrar cerca de 2000 objetos complexos, usando caminhos definidos dinamicamente com níveis de profundidade de 3 a 4.

Uma metodologia semelhante poderia ser usada para criar funções de setter, é claro:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Kevin Crumley
fonte
1
se você precisar desreferenciar os mesmos caminhos por um longo período, o link jsperf.com acima mostra um exemplo de como salvar e procurar a função posteriormente. O ato de chamar o construtor Function é bastante lento, portanto, o código de alto desempenho deve memorizar os resultados para evitar repeti-lo, se possível.
precisa
7

Sugiro dividir o caminho, iterá-lo e reduzir o objeto que você possui. Esta proposta trabalha com um valor padrão para propriedades ausentes.

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
fonte
6

Observe que se você já estiver usando o Lodash, poderá usar as funções propertyou get:

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

Sublinhado também tem uma propertyfunção, mas não suporta notação de ponto.

Tamlyn
fonte
1
Parece que o sublinhado não suporta notação de ponto. Resposta atualizada.
Tamlyn
5

Outras propostas são um pouco enigmáticas, então pensei em contribuir:

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
fonte
5

Você pode usar a biblioteca disponível no npm, o que simplifica esse processo. 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
fonte
1
Obrigado por trazer esse assunto a nossa atenção. Parece muito útil.
Nevf
E é por isso que todo projeto injeta 10000 dependências :-).
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

Isso é muito código quando comparado à evalmaneira mais simples de fazer isso, mas como Simon Willison diz, você nunca deve usar eval .

Além disso, o JSFiddle .

McKayla
fonte
Ótimo, isso funciona muito bem e é mais curto que o CD Sanchez. Obrigado.
nevf 18/06/11
valor (a, 'bbbbb') não retorna indefinido
frumbert 13/10
4

Estendi a resposta elegante por ninjagecko para que a função lide com referências pontilhadas e / ou de estilo de matriz e para que uma string vazia faça com que o objeto pai seja retornado.

Aqui está:

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

Veja meu exemplo de trabalho do jsFiddle aqui: http://jsfiddle.net/sc0ttyd/q7zyd/

Sc0ttyD
fonte
Solução realmente boa. Há apenas um problema, ele assume que a []notação é sempre para matrizes. não pode haver chaves de objeto representado dessa forma também, por exemplo obj['some-problem/name'].list[1]Para corrigir isso eu tinha de atualização arr_dereffunção 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
Embora hoje em dia eu não faça isso. Eu usaria o 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
fonte
Obrigado por todas as respostas rápidas. Não goste das soluções eval (). Esta e as postagens semelhantes ficam melhores. No entanto, ainda estou tendo um problema. Estou tentando definir o valor obj.ab = novo valor. Para ser preciso, o valor de b é uma função, por isso preciso usar obj.ab (new_value). A função é chamada, mas o valor não está definido. Eu acho que é uma questão de escopo, mas ainda estou cavando. Sei que isso está fora do escopo da pergunta original. Meu código está usando o Knockout.js eb é um ko.observable.
Nevf
@ Nevev: eu adicionei uma segunda função que eu acho que faz o que você quer. Você pode personalizá-lo ao seu gosto, dependendo do comportamento desejado (por exemplo, ele deve criar os objetos se eles não existirem ?, etc.).
Cristian Sanchez
@ nevf Mas o meu faz isso com uma função. ; D
McKayla
obrigado pela atualização que eu pude usar. @tylermwashburn - e obrigado por sua implementação mais curta, que também funciona. Tenha um ótimo w / e tudo.
Nevf
2

Você pode obter o valor de um membro do objeto por notação de ponto com uma única linha de código:

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

No seu caso:

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

Para simplificar, você pode escrever uma função como esta:

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

Explicação:

O construtor Function cria um novo objeto Function. Em JavaScript, toda função é realmente um objeto Function. A sintaxe para criar uma função explicitamente com o construtor Function é:

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

onde arguments(arg1 to argN)deve haver uma sequência que corresponda a um identificador javaScript válido e functionBodyé uma sequência que contém as instruções javaScript que compreendem a definição da função.

No nosso caso, aproveitamos a vantagem do corpo da função string para recuperar o membro do objeto com notação de ponto.

Espero que ajude.

Harish Anchu
fonte
Você pode explicar exatamente o que isso está fazendo? E como você passaria 'ab' etc. como um parâmetro de função?
Nevf 12/04/2015
1
Eu dei um +1, mas o JSLint adverte que "o construtor Function é uma forma de avaliação" .
gabe
2

Resposta GET / SET que também funciona em reagir nativo (você não pode atribuir a Object.prototypeatualmente):

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
fonte
1

Copiei o seguinte da resposta de Ricardo Tomasi e modifiquei para também criar subobjetos que ainda não existem conforme necessário. É um pouco menos eficiente (maisif cria objetos vazios), mas deve ser muito bom.

Além disso, nos permitirá fazer Object.prop(obj, 'a.b', false)onde não podíamos antes. Infelizmente, ele ainda não nos permite atribuir undefined... Ainda não sabemos como proceder.

/**
 * 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.
fonte
1

Se você deseja converter qualquer objeto que contenha chaves de notação de ponto em uma versão em matriz dessas chaves, você pode usá-lo.


Isso converterá algo como

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

para

{
  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
fonte
Não processou os valores com "brothers.0.one" como JSON.
Karthick #
Você tem alguma sugestão para processar JSON mais complexo com coleção de matrizes?
Karthick
0

Aqui está o meu código sem usar eval. É fácil de entender também.

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
fonte
0

Sim, foi perguntado há 4 anos e sim, estender protótipos de base geralmente não é uma boa ideia, mas, se você mantiver todas as extensões em um só lugar, elas poderão ser úteis.
Então, aqui está a minha maneira de fazer isso.

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

Agora você poderá obter propriedades aninhadas em qualquer lugar sem importar o módulo com a função ou a função copiar / colar.

UPD.Exemplo:

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

UPD 2. A próxima extensão interrompe o mangusto no meu projeto. Também li que pode ter quebrado o jquery. Portanto, nunca faça da próxima maneira

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

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

    return result;
};
Michael Kapustey
fonte
0

Aqui está a minha implementação

Implementação 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));
}

Implementação 2 (usando matriz reduzida em vez de fatia)

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

Exemplos:

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

ele também pode manipular objetos dentro de matrizes como para

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

Esta é a minha solução estendida proposta por: ninjagecko

Para mim, a simples notação de seqüência de caracteres não foi suficiente; portanto, a versão abaixo suporta coisas como:

índice (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
fonte
0

Correndo o risco de bater em um cavalo morto ... acho isso mais útil ao atravessar objetos aninhados para fazer referência a onde você está com relação ao objeto base ou a um objeto semelhante com a mesma estrutura. Para esse fim, isso é útil com uma função de travessia de objeto aninhado. Observe que eu usei uma matriz para manter o caminho. Seria trivial modificar isso para usar um caminho de string ou uma matriz. Observe também que você pode atribuir "indefinido" ao valor, diferentemente de outras implementações.

/*
 * 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
fonte
0

Eu usei esse código no meu projeto

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
fonte
0

Poucos anos depois, descobri isso que lida com escopo e matriz. por exemploa['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
fonte
-1

Não está claro qual é a sua pergunta. Dado o seu objeto, obj.a.bdaria a você "2" exatamente como é. Se você quiser manipular a string para usar colchetes, faça o seguinte:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Mark Eirich
fonte
1
Isso não funciona a.b.ce realmente não realiza o que eles querem. Eles querem o valor, não um evalcaminho.
McKayla
Agora eu o corrigi para que funcione a.b.c, mas você está certo, aparentemente ele queria obter / definir o valor da propriedade em obj.a.b. A questão foi confuso para mim, já que ele disse que queria "converter a string" ....
Mark Eirich
Bom trabalho. :) Foi um pouco vago. Você fez um bom trabalho de conversão.
McKayla
-1

aqui estão os meus 10 centavos de caso, a função abaixo será configurada com base no caminho fornecido, .. com certeza você pode melhorá-lo, remova || e substitua-o por Object.hasOwnPropertyse você se importa com valores falsos por engano,

Eu testei com a.b.ce ab2.c {a:{b:[0,1,{c:7}]}}e seus trabalhos para definir e obter :).

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
fonte