Acessando objetos e arrays JavaScript aninhados pelo caminho da string

454

Eu tenho uma estrutura de dados 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'
        }
    ]
};

E eu gostaria de acessar os dados usando estas variáveis:

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

part1name deve ser preenchido com someObject.part1.nameo valor de "Part 1". A mesma coisa com part2quantity, que preencheu 60.

Existe alguma maneira de conseguir isso com javascript puro ou JQuery?

Komaruloh
fonte
Não sabe o que está perguntando aqui? Deseja consultar part1.name e devolver o texto "part1.name"? Ou você quer um meio de obter o valor armazenado em part1.name?
BonyT
você já tentou fazer como var part1name = someObject.part1name;`#
2127 Rafay Rafay
1
@ TonyT: Eu quero consultar someObject.part1.name e retornar o valor dele ("Parte 1"). No entanto, quero que a consulta (eu chamei de "a chave") seja armazenada em uma variável 'part1name'. Obrigado pela sua resposta. @ 3nigma: Eu certamente tenho. Mas essa não é minha intenção. Obrigado pela resposta.
Komaruloh
1
na resposta duplicado, eu amo a resposta da ARJ stackoverflow.com/questions/8817394/...
Steve Preto

Respostas:

520

Acabei de fazer isso com base em um código semelhante que já tinha, 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');

Veja uma demonstração de trabalho em http://jsfiddle.net/alnitak/hEsys/

EDIT Alguns notaram que esse código geraria um erro se passasse uma string em que os índices mais à esquerda não correspondam a uma entrada aninhada corretamente dentro do objeto. Esta é uma preocupação válida, mas o IMHO melhor endereçou com um try / catchbloco ao chamar, em vez de ter essa função silenciosamente retornar undefinedpara um índice inválido.

Alnitak
fonte
19
Isso funciona lindamente. Contribua com isso para a Internet, envolvendo-o como um pacote de nós.
t3dodson
14
@ t3dodson Acabei de fazer: github.com/capaj/object-resolve-path apenas lembre-se de que isso não é agradável quando o nome da sua propriedade contém '[]' em si. O Regex o substituirá por '.' e ele não funciona como esperado
Capaj
20
coisas boas; usando a biblioteca lodash, também se pode fazer:_.get(object, nestedPropertyString);
ian
17
Provavelmente, isso se perderá no mar de comentários; no entanto, ocorrerá um erro se você tentar abordar uma propriedade que não existe. Então 'part3[0].name.iDontExist'. Adicionar uma verificação para ver se ohá um objeto na if incorreção do problema. (Como você faz isso é com você). Veja o violino atualizado: jsfiddle.net/hEsys/418
ste2425
2
Isto é tão ouro. Temos um aplicativo baseado em configuração e isso é meio útil! Obrigado!
Christian Esperar
182

Esta é a solução que eu 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)
}

Exemplo 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

Limitações:

  • Não é possível usar colchetes ( []) para índices de matriz - embora a especificação de índices de matriz entre o token do separador (por exemplo, .) funcione corretamente, como mostrado acima.
speigg
fonte
7
usando reduzir é uma excelente solução (também se pode utilizar _.reduce()a partir da biblioteca sublinhado ou lodash)
Alp
3
Eu acho que selfé provavelmente indefinido aqui. Você quer dizer this?
Platinum Azure
2
Aqui está a minha complemento para valores definidos pelo caminho: pastebin.com/jDp5sKT9
mroach
1
Alguém sabe como portar isso para o TypeScript?
Adam Plocher
1
@ SC1000 boa ideia. Esta resposta foi escrita antes que os parâmetros padrão estivessem disponíveis na maioria dos navegadores. Vou atualizá-lo para "function resolution (path, obj = self)", pois a referência ao objeto global como padrão é intencional.
speigg 15/08/19
179

Agora isso é suportado pelo lodash usando _.get(obj, property). Vejo https://lodash.com/docs#get

Exemplo dos documentos:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// → 3

_.get(object, ['a', '0', 'b', 'c']);
// → 3

_.get(object, 'a.b.c', 'default');
// → 'default'
Ian Walker-Sperber
fonte
9
Essa deve ser a única resposta aceita, porque é a única que trabalha para a sintaxe de ponto e colchete e não falha quando temos '[]' na cadeia de caracteres de uma chave no caminho.
Capaj
7
Este. Além disso, ele oferece suporte_.set(...)
Josh C.
o que acontece se o objeto não for encontrado?
DDave
@DDave se o valor passado como o objeto for indefinido ou não for um objeto, _.getmostrará o mesmo comportamento de quando nenhuma chave for encontrada no objeto fornecido. por exemplo _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". No entanto, esse comportamento não está definido nos documentos, portanto, sujeitos a alterações.
Ian Walker-Sperber
5
@Capaj você está brincando? E quem não quer / não pode usar lodash?
Andre Figueiredo
74

ES6 : Apenas uma linha no Vanila JS (ele retorna nulo se não encontrar, em vez de dar erro):

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

Ou exemplo:

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

Com operador de encadeamento opcional :

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

Para uma função pronta para usar que também reconhece número falso, 0 e negativo e aceita valores padrão como parâmetro:

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

Exemplo 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

Bônus :

Para definir um caminho (solicitado por @ rob-gordon), você pode usar:

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

Exemplo:

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

Matriz de acesso com [] :

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

Exemplo:

const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
Adriano Spadoni
fonte
2
Eu amo essa técnica. Isso é realmente confuso, mas eu queria usar essa técnica para atribuição. 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
Eu gosto da idéia de usar reduzir, mas sua lógica parece fora para 0, undefinede nullvalores. {a:{b:{c:0}}}retorna em nullvez de 0. Talvez a verificação explícita de nulo ou indefinido resolva esses problemas. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku
Olá @SmujMaiku, a função "pronto para usar" retorna corretamente para '0', 'indefinido' e 'nulo', acabei de testar no console: resolvePath ({a: {b: {c: 0}}}, ' abc ', nulo) => 0; É verificar se a chave existe em vez do próprio valor que evitar mais de um cheque
Adriano Spadoni
aqui defaultValue não funcionou, usando Reflect.has(o, k) ? ...(ES6 Reflect.has ) trabalhado, apesar de
Andre Figueiredo
setPathdefinirá o valor na primeira ocorrência do último seletor. Por exemplo, let o = {}; setPath(o, 'a.a', 0)resulta em em {a: 0}vez de {a: {a: 0}}. veja este post para uma solução.
Pkfm 24/03/19
62

Você teria que analisar a string você mesmo:

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

Isso exigia que você também definisse índices de matriz com notação de ponto:

var part3name1 = "part3.0.name";

Isso facilita a análise.

DEMO

Felix Kling
fonte
@ Felix Kling: Sua solução me fornece o que eu preciso. E eu agradeço muito por isso. Mas Alnitak também fornece maneiras diferentes e parece funcionar também. Como só posso escolher uma resposta, escolherei a resposta Alnitak. Não que a solução dele seja melhor que você ou algo assim. De qualquer forma, eu realmente aprecio sua solução e esforço que você deu.
Komaruloh
1
@Komaruloh: Ah, eu pensei que você sempre pode votar nas respostas da sua própria pergunta .... de qualquer maneira eu estava brincando mais ou menos, não preciso de mais reputação;) Feliz codificação!
Felix Kling
1
@ Felix Kling: Você precisa de pelo menos 15 reputação para votar. :) Eu acredito que você não precisa de mais reputação com 69k +. Obrigado
Komaruloh
@Felix FWIW - a conversão da []sintaxe para a sintaxe da propriedade é bastante trivial.
Alnitak
4
Se você alterar o loop while para while (l > 0 && (obj = obj[current]) && i < l), esse código funcionará também para strings sem pontos.
Snea 17/08/14
39

Também funciona para matrizes / matrizes dentro do objeto. Defensivo contra valores inválidos.

/**
 * Retrieve nested item from object/array
 * @param {Object|Array} obj
 * @param {String} path dot separated
 * @param {*} def default value ( if result undefined )
 * @returns {*}
 */
function path(obj, path, def){
    var i, len;

    for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
        if(!obj || typeof obj !== 'object') return def;
        obj = obj[path[i]];
    }

    if(obj === undefined) return def;
    return obj;
}

//////////////////////////
//         TEST         //
//////////////////////////

var arr = [true, {'sp ace': true}, true]

var obj = {
  'sp ace': true,
  arr: arr,
  nested: {'dotted.str.ing': true},
  arr3: arr
}

shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>

TheZver
fonte
10
Obrigado, esta é a melhor e mais eficiente
Dominic
@ Endless, eu gostaria de enfatizar o caminho deve separar os itens com pontos. Aparelho não vai funcionar. Ou seja, para acessar o primeiro item da matriz, use "0.sp ace".
TheZver 27/03
26

usando eval:

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

wrap para retornar indefinido por erro

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

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

Por favor, use o bom senso e cautela ao exercer o poder da avaliação. É um pouco como um sabre de luz, se você ativá-lo, há 90% de chance de cortar um membro. Não é para todos.

Shanimal
fonte
7
A avaliação ou não da avaliação depende de onde os dados da cadeia de propriedades são provenientes. Duvido que você tenha algum motivo para se preocupar com a invasão de hackers por meio de uma estática "var p = 'abc'; eval (p);" digite chamada. É uma ideia perfeitamente boa para isso.
James Wilkins
17

Você pode obter um valor de um membro profundo do objeto com notação de ponto sem nenhuma biblioteca JavaScript externa com o seguinte truque simples:

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

No seu caso para obter valor part1.namea partir de someObjectapenas fazer:

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

Aqui está uma demonstração simples de violino: https://jsfiddle.net/harishanchu/oq5esowf/

Harish Anchu
fonte
3
função valor_ profundo (obj, caminho) {retornar nova Função ('o', 'retornar o.' + caminho) (obj); }
ArcangelZith
14

Provavelmente isso nunca verá a luz do dia ... mas aqui está mesmo assim.

  1. Substituir [] sintaxe do suporte por.
  2. Dividir em . personagem
  3. Remover cadeias de caracteres em branco
  4. Encontre o caminho (caso contrário undefined)

// "one liner" (ES6)

const deep_value = (obj, path) => 
  path
    .replace(/\[|\]\.?/g, '.')
    .split('.')
    .filter(s => s)
    .reduce((acc, val) => acc && acc[val], obj);
    
// ... and that's it.

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

console.log(deep_value(someObject, "part1.name"));               // Part 1
console.log(deep_value(someObject, "part2.qty"));                // 60
console.log(deep_value(someObject, "part3[0].name"));            // Part 3A

Nick Grealy
fonte
11

É um forro com 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"

Ou melhor ainda ...

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

Ou versão ES6 com redução ...

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

Plunkr

James
fonte
7

Eu acho que você está pedindo isso:

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

Você pode estar perguntando isso:

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

Ambos irão funcionar


Ou talvez você esteja pedindo isso

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

var part1name = someObject[partName][nameStr];

Finalmente, você pode estar pedindo por isso

var partName = "part1.name";

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

var part1name = someObject[partBits[0]][partBits[1]];
Hogan
fonte
Acho que o OP está pedindo a última solução. No entanto, as strings não têm Splitmétodo, mas sim split.
duri
Na verdade, eu estava perguntando o último. A variável partName é preenchida com uma string indicando a estrutura da chave a ser valorizada. Sua solução parece fazer sentido. No entanto, talvez eu precise modificar para obter maior profundidade nos dados, como nível 4-5 e mais. E eu estou querendo saber se posso tratar a matriz e objeto uniformemente com isso?
Komaruloh
7

Aqui, ofereço mais maneiras, que parecem mais rápidas em muitos aspectos:

Opção 1: Dividir a sequência. ou [ou] ou 'ou ", inverta, pule itens vazios.

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

Opção 2 (a mais rápida de todas, exceto eval): Varredura de caracteres de baixo nível (sem regex / split / etc, apenas uma varredura rápida de caracteres). Nota: Este não suporta aspas 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)

Opção 3: ( novo : opção 2 expandida para oferecer suporte a cotações - um pouco mais lenta, mas ainda 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

"eval (...)" ainda é rei (o que é desempenho). Se você possui caminhos de propriedade diretamente sob seu controle, não deve haver nenhum problema com o uso de 'eval' (especialmente se a velocidade for desejada). Se estiver puxando caminhos de propriedade "por cima do fio" ( na linha !? Lol: P), sim, use outra coisa para ser seguro. Apenas um idiota diria para nunca usar "eval", pois existem boas razões para usá-lo. Além disso, "é usado no analisador JSON de Doug Crockford ". Se a entrada for segura, não haverá problemas. Use a ferramenta certa para o trabalho certo, é isso.

James Wilkins
fonte
6

Caso alguém esteja visitando esta pergunta em 2017 ou mais tarde e procurando uma maneira fácil de lembrar , aqui está uma publicação elaborada no blog Acessando Objetos Aninhados em JavaScript sem ser enganada por

Não é possível ler a propriedade 'foo' de indefinido erro

Acessar objetos aninhados usando matriz Reduzir

Vamos pegar esta estrutura de exemplo

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

Para poder acessar matrizes aninhadas, você pode escrever seu próprio utilitário de redução de matrizes.

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.

Há também um excelente tipo de manipulação de typy mínimo de biblioteca que faz tudo isso para você.

Com o typy, seu código ficará assim

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

Disclaimer: Eu sou o autor deste pacote.

Dinesh Pandiyan
fonte
6

AngularJS

A abordagem de Speigg é muito organizada e limpa, embora eu tenha encontrado essa resposta ao procurar a solução de acessar as propriedades do escopo AngularJS $ pelo caminho da string e, com uma pequena modificação, ele faz o trabalho:

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

Basta colocar esta função no seu controlador raiz e usá-la em qualquer escopo filho como este:

$scope.resolve( 'path.to.any.object.in.scope')
nesinervink
fonte
Veja AngularJS tem$scope.$eval uma outra maneira de fazê-lo com AngularJS.
georgeawg 5/03
3

Ainda não encontrei um pacote para executar todas as operações com um caminho de string, então acabei escrevendo meu próprio pacote rápido que suporta insert (), get () (com retorno padrão), set () e remove ( ) operações.

Você pode usar notação de ponto, colchetes, índices numéricos, propriedades de números de sequência e teclas com caracteres que não sejam palavras. Uso simples abaixo:

> var jsocrud = require('jsocrud');

...

// Get (Read) ---
> var obj = {
>     foo: [
>         {
>             'key w/ non-word chars': 'bar'
>         }
>     ]
> };
undefined

> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'

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

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

Kyle
fonte
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 com

getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
Vitim.us
fonte
3

Função simples, permitindo um caminho de string ou array.

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

OU

console.log(get(obj, ['a', 'b', 'c'])); //foo
Ben
fonte
Se você deseja publicar o código como resposta, explique por que o código responde à pergunta.
Tieson T.
2

Agora, existe um npmmódulo para fazer isso: https://github.com/erictrinh/safe-access

Exemplo de uso:

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

Embora reduzir seja bom, fico surpreso que ninguém tenha usado cada um:

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

Teste

Flavien Volken
fonte
Você nem está verificando se obj [key] realmente existe. Não é confiável.
Carles Alcolea
@CarlesAlcolea, por padrão, js não verificará se a chave de um objeto existe: a.b.cgerará uma exceção se não houver propriedade bem seu objeto. Se você precisa de algo silenciosamente descartando o caminho-chave errada (que eu não recomendo), você ainda pode substituir o forEach com um presentekeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken
Eu corro através dela um objeto que estava faltando uma chaveta, meu mau :)
Carles Alcolea
1

Apenas tive a mesma pergunta recentemente e utilizou com sucesso https://npmjs.org/package/tea-properties, que tambémset aninhava objetos / matrizes:

pegue:

var o = {
  prop: {
    arr: [
      {foo: 'bar'}
    ]
  }
};

var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');

assert(value, 'bar'); // true

conjunto:

var o = {};

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

assert(o.prop.arr[0].foo, 'bar'); // true
abernier
fonte
"Este módulo foi descontinuado. Use chaijs / pathval."
Patrick Fisher
1

Inspirado na resposta de @ webjay: https://stackoverflow.com/a/46008856/4110122

Eu criei essa função que você pode usar para obter / definir / desabilitar qualquer valor no 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 usá-lo:

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

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

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

Estou desenvolvendo uma loja on-line com o React. Tentei alterar os valores no objeto de estado copiado para atualizar o estado original com ele no envio. Os exemplos acima não funcionaram para mim, porque a maioria deles muda a estrutura do objeto copiado. Encontrei um exemplo prático da função para acessar e alterar valores das propriedades profundas do objeto aninhado: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Aqui está:

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

Com base na resposta de Alnitak .

Embrulhei o polyfill em um cheque e reduzi a função a uma única redução encadeada.

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

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

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

Mr. Polywhirl
fonte
0

Se você precisar acessar diferentes chaves aninhadas sem conhecê-las no momento da codificação (será trivial tratá-las), poderá usar o acessador de notação de matriz:

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

Eles são equivalentes ao acessador de notação de ponto e podem variar em tempo de execução, por exemplo:

var part = 'part1';
var property = 'name';

var part1name = someObject[part][property];

é equivalente a

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

ou

var part1name = someObject.part1.name;

Espero que este endereço sua pergunta ...

EDITAR

Não usarei uma string para manter uma espécie de consulta xpath para acessar um valor de objeto. Como você precisa chamar uma função para analisar a consulta e recuperar o valor, eu seguiria outro caminho (não:

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

ou, se você estiver preocupado com o método apply

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

As funções são mais curtas e claras, o intérprete verifica se há erros de sintaxe e assim por diante.

A propósito, acho que uma tarefa simples feita no momento certo será suficiente ...

Eineki
fonte
Interessante. Mas no meu caso, o someObject ainda é inicializado quando atribuo valor a part1name. Eu só conheço a estrutura. É por isso que eu uso string para descrever a estrutura. E na esperança de poder usá-lo para consultar meus dados de someObject. Obrigado por compartilhar seu pensamento. :)
Komaruloh
@ Komaruloh: Eu acho que você escreveria que o objeto ainda não foi inicializado quando você criar suas variáveis. A propósito, não entendi o motivo, por que você não pode fazer a tarefa no momento apropriado?
Eineki
Desculpe por não mencionar que someObject ainda não foi inicializado. Por esse motivo, someObject é buscado via serviço da web. E eu quero ter uma matriz de cabeçalho que consiste em part1name, part2qty, etc. Para que eu pudesse percorrer a matriz do cabeçalho e obter o valor desejado com base no valor part1name como a 'chave' / caminho para someObject.
Komaruloh
0

As soluções aqui são apenas para acessar as chaves profundamente aninhadas. Eu precisava de um para acessar, adicionar, modificar e excluir as chaves. Isto é o que eu vim com:

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: caminho em uma matriz. Você pode substituí-lo pelo seu string_path.split(".").
  • type_of_function: 0 para acessar (não passe nenhum valor para value), 0 para adicionar e modificar. 1 para excluir.
ayushgp
fonte
0

Partindo da resposta 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;
  };

}

Isso permite que você defina um valor também!

Eu criei um pacote npm e github com isso também

Tamb
fonte
0

Em vez de uma string, uma matriz pode ser usada para endereçar objetos e matrizes aninhados, por exemplo: ["my_field", "another_field", 0, "last_field", 10]

Aqui está um exemplo que alteraria um campo com base nessa representação de matriz. Estou usando algo parecido no react.js para campos de entrada controlada que alteram o estado das estruturas aninhadas.

let state = {
        test: "test_value",
        nested: {
            level1: "level1 value"
        },
        arr: [1, 2, 3],
        nested_arr: {
            arr: ["buh", "bah", "foo"]
        }
    }

function handleChange(value, fields) {
    let update_field = state;
    for(var i = 0; i < fields.length - 1; i++){
        update_field = update_field[fields[i]];
    }
    update_field[fields[fields.length-1]] = value;
}

handleChange("update", ["test"]);
handleChange("update_nested", ["nested","level1"]);
handleChange(100, ["arr",0]);
handleChange('changed_foo', ["nested_arr", "arr", 3]);
console.log(state);
Jodo
fonte
0

Com base em uma resposta anterior, criei uma função que também pode lidar com colchetes. Mas não há pontos dentro deles devido à divisão.

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

// (IE9+) Two steps

var pathString = "[0]['property'].others[3].next['final']";
var obj = [{
  property: {
    others: [1, 2, 3, {
      next: {
        final: "SUCCESS"
      }
    }]
  }
}];

// Turn string to path array
var pathArray = pathString
    .replace(/\[["']?([\w]+)["']?\]/g,".$1")
    .split(".")
    .splice(1);

// Add object prototype method
Object.prototype.path = function (path) {
  try {
    return [this].concat(path).reduce(function (f, l) {
      return f[l];
    });
  } catch (e) {
    console.error(e);
  }
};

// usage
console.log(obj.path(pathArray));
console.log(obj.path([0,"doesNotExist"]));

Oboo Cheng
fonte
0

Trabalhando com Underscore's propertyou 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>

Boa sorte...

Akash
fonte