Dostęp do zagnieżdżonych obiektów JavaScript i aray według ścieżki ciągu


454

Mam taką strukturę danych:

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

I chciałbym uzyskać dostęp do danych przy użyciu tych zmiennych:

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

part1name powinna być wypełniona someObject.part1.namewartością, czyli „Part 1”. To samo z part2quantity, który wypełniał 60.

Czy można to osiągnąć za pomocą czystego javascript lub JQuery?


Nie jesteś pewien, o co tu pytasz? Chcesz mieć możliwość zapytania o part1.name i zwrócenia tekstu „part1.name”? A może potrzebujesz sposobu, aby uzyskać wartość przechowywaną w part1.name?
BonyT

próbowałeś robić jak var part1name = someObject.part1name;`
Rafay

1
@BonyT: Chcę wysłać zapytanie do someObject.part1.name i zwrócić jej wartość („Część 1”). Chcę jednak, aby zapytanie (nazwałem to „kluczem”) było przechowywane w zmiennej „part1name”. Dzięki za odpowiedź. @ 3nigma: Z pewnością tak. Ale to nie jest moja intencja. Dziękuję za odpowiedź.
Komaruloh

1
w zduplikowanej odpowiedzi uwielbiam odpowiedź fyr stackoverflow.com/questions/8817394/...
Steve Black

Odpowiedzi:


520

Właśnie stworzyłem to w oparciu o jakiś podobny kod, który już miałem, wygląda na to, że działa:

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

Stosowanie::

Object.byString(someObj, 'part3[0].name');

Zobacz działające demo na http://jsfiddle.net/alnitak/hEsys/

EDIT niektórzy zauważyli, że ten kod zgłosi błąd, jeśli przekaże ciąg, w którym indeksy najbardziej po lewej stronie nie odpowiadają poprawnie zagnieżdżonej pozycji w obiekcie. Jest to ważna kwestia, ale IMHO najlepiej rozwiązywać za pomocą try / catchbloku podczas wywoływania, zamiast tego, aby ta funkcja cicho zwracała undefinedniepoprawny indeks.


19
To działa pięknie. Przekaż to do Internetu, pakując w pakiet węzłów.
t3dodson

14
@ t3dodson Właśnie to zrobiłem: github.com/capaj/object-resolve-path po prostu pamiętaj, że to nie gra dobrze, gdy nazwa twojej nieruchomości zawiera „[]” w sobie. Regex zastąpi go „.” i to nie działa zgodnie z oczekiwaniami
Capaj,

20
świetne rzeczy; korzystając z biblioteki lodash, można również:_.get(object, nestedPropertyString);
ian

17
Prawdopodobnie zgubi się to w morzu komentarzy, jednak błędy będą występować, jeśli spróbujesz zaadresować właściwość, która nie istnieje. Tak 'part3[0].name.iDontExist'. Dodanie zaznaczenia, aby sprawdzić, czy ojest obiektem w if inrozwiązuje problem. (To, jak sobie poradzisz, zależy od ciebie). Zobacz zaktualizowane skrzypce: jsfiddle.net/hEsys/418
ste2425,

2
To jest takie złote. Mamy aplikację opartą na konfiguracji i jest to trochę pomocne! Dzięki!
Christian Esperar

182

Oto rozwiązanie, którego używam:

function resolve(path, obj=self, separator='.') {
    var properties = Array.isArray(path) ? path : path.split(separator)
    return properties.reduce((prev, curr) => prev && prev[curr], obj)
}

Przykładowe użycie:

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

Ograniczenia:

  • Nie można używać nawiasów ( []) do indeksów tablic - chociaż określenie indeksów tablic między tokenem separatora (np. .) Działa dobrze, jak pokazano powyżej.

7
użycie redukcji jest doskonałym rozwiązaniem (można również użyć _.reduce()z biblioteki podkreślenia lub biblioteki lodash)
Alp

3
Myślę, że selfprawdopodobnie nie jest tutaj zdefiniowany. Czy masz na myśli this?
Platinum Azure,

2
Oto moje uzupełnienie do ustawiania wartości według ścieżki: pastebin.com/jDp5sKT9
mroach

1
Czy ktoś wie, jak przenieść to na TypeScript?
Adam Plocher

1
@ SC1000 dobry pomysł. Ta odpowiedź została napisana, zanim domyślne parametry były dostępne w większości przeglądarek. Zaktualizuję go do „function resolver (path, obj = self)”, ponieważ odwołanie do obiektu globalnego jako domyślnego jest zamierzone.
speigg

179

Jest to teraz obsługiwane przez lodash przy użyciu _.get(obj, property). Zobacz https://lodash.com/docs#get

Przykład z dokumentów:

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'

9
To powinna być jedyna akceptowana odpowiedź, ponieważ jest to jedyna działająca zarówno dla składni kropkowej, jak i nawiasowej. Nie zawodzi, gdy w ciągu klucza na ścieżce znajduje się „[]”.
Capaj,

7
To. Ponadto obsługuje_.set(...)
Josh C.

co się stanie, jeśli objet nie zostanie znaleziony?
DDave

@DDave, jeśli wartość przekazana jako obiekt jest niezdefiniowany lub nie jest obiektem, _.getpokaże takie samo zachowanie, jak wtedy, gdy w podanym obiekcie nie zostanie znaleziony żaden klucz. np _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Jednak to zachowanie nie jest zdefiniowane w dokumentacji, więc może ulec zmianie.
Ian Walker-Sperber

5
@Capaj, ty żartujesz? A kto nie chce / nie może używać lodash?
Andre Figueiredo

74

ES6 : Tylko jedna linia w Vanila JS (zwraca null, jeśli nie znajduje zamiast błędu):

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

Lub przykład:

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

W przypadku gotowej do użycia funkcji, która rozpoznaje również fałsz, 0 i liczbę ujemną i przyjmuje wartości domyślne jako parametr:

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

Przykład użycia:

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

Bonus :

Aby ustawić ścieżkę (wymagane przez @ rob-gordon), możesz użyć:

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

Przykład:

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

Dostęp do tablicy za pomocą [] :

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

Przykład:

const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1

2
Uwielbiam tę technikę. To jest naprawdę bałagan, ale chciałem użyć tej techniki do zadań. 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
I podoba mi się pomysł z wykorzystaniem zmniejszyć ale logika wydaje się za 0, undefineda nullwartości. {a:{b:{c:0}}}zwraca nullzamiast 0. Być może jawne sprawdzenie wartości null lub niezdefiniowanej rozwiąże te problemy. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku

Cześć @SmujMaiku, funkcja „gotowy do użycia” zwraca poprawnie dla „0”, „undefined” i „null”, właśnie przetestowałem na konsoli: resolPath ({a: {b: {c: 0}}}, ' abc ', null) => 0; Sprawdza, czy klucz istnieje zamiast samej wartości, co pozwala uniknąć więcej niż jednego sprawdzenia
Adriano Spadoni

tutaj defaultValue nie działało, ale działało Reflect.has(o, k) ? ...(ES6 Reflect.has )
Andre Figueiredo

setPathustawi wartość przy pierwszym wystąpieniu ostatniego selektora. Na przykład let o = {}; setPath(o, 'a.a', 0)wyniki {a: 0}zamiast {a: {a: 0}}. zobacz ten post dla rozwiązania.
pkfm

62

Musisz sam parsować ciąg:

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

Wymagało to również zdefiniowania indeksów tablic z notacją kropkową:

var part3name1 = "part3.0.name";

Ułatwia to analizę.

PRÓBNY


@Felix Kling: Twoje rozwiązanie zapewnia mi to, czego potrzebuję. I bardzo za to dziękuję. Ale Alnitak oferuje również różne sposoby i wydaje się, że działa. Ponieważ mogę wybrać tylko jedną odpowiedź, wybiorę odpowiedź Alnitak. Nie to, że jego rozwiązanie jest lepsze od ciebie czy coś takiego. W każdym razie naprawdę doceniam twoje rozwiązanie i wysiłek, który włożyłeś.
Komaruloh

1
@Komaruloh: Oh, myślałem, że zawsze możesz głosować odpowiedzi na swoje własne pytanie ... w każdym razie żartowałem, nie potrzebuję więcej reputacji;) Szczęśliwego kodowania!
Felix Kling

1
@Felix Kling: Aby uzyskać więcej głosów, potrzebujesz co najmniej 15 reputacji. :) Wierzę, że nie potrzebujesz więcej reputacji z 69k +. Dzięki
Komaruloh,

@Felix FWIW - konwersja []składni na składnię właściwości jest dość trywialna.
Alnitak

4
Jeśli zmienisz pętlę while na, while (l > 0 && (obj = obj[current]) && i < l)ten kod działa również dla ciągów bez kropek.
Snea

39

Działa również dla tablic / tablic wewnątrz obiektu. Obrona przed nieprawidłowymi wartościami.

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

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

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

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

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

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

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


10
Dzięki, to najlepsza i najbardziej wydajna odpowiedź - jsfiddle.net/Jw8XB/1
Dominic

@ Niekończące się, chciałbym podkreślić, że ścieżka powinna oddzielić elementy kropkami. Aparat ortodontyczny nie będzie działać. Aby uzyskać dostęp do pierwszego elementu w tablicy, użyj „0.sp ace”.
TheZver

26

przy użyciu eval:

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

zawiń, aby zwrócić niezdefiniowane po błędzie

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

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

Korzystając z mocy evalu, zachowaj zdrowy rozsądek i ostrożność. To trochę jak lekka szabla, jeśli ją włączysz, masz 90% szansy na rozerwanie kończyny. To nie jest dla wszystkich.


7
To, czy eval jest dobrym pomysłem, zależy od tego, skąd pochodzą dane ciągu właściwości. Wątpię, czy masz powód do niepokoju dla włamywaczy za pomocą statycznego „var p = 'abc”; eval (p); ” wpisz połączenie. To doskonały pomysł.
James Wilkins

17

Możesz uzyskać wartość elementu głębokiego obiektu z notacją kropkową bez żadnej zewnętrznej biblioteki JavaScript za pomocą prostej sztuczki:

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

W twoim przypadku, aby uzyskać wartość part1.nameod someObjectpo prostu zrób:

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

Oto proste demo skrzypce: https://jsfiddle.net/harishanchu/oq5esowf/


3
function deep_value (obj, path) {return new Function ('o', 'return o.' + path) (obj); }
ArcangelZith

14

Prawdopodobnie nigdy nie ujrzy światła dziennego ... ale i tak jest.

  1. Zamień []składnię nawiasu na.
  2. Podziel na .postać
  3. Usuń puste ciągi
  4. Znajdź ścieżkę (w przeciwnym razie undefined)

// "one liner" (ES6)

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

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

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


11

Jest to jedna wkładka z 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"

A nawet lepiej ...

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

Lub wersja ES6 w / zmniejsz ...

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

Plunkr


7

Myślę, że o to prosisz:

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

Możesz o to poprosić:

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

Oba będą działać


A może o to prosisz

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

var part1name = someObject[partName][nameStr];

Wreszcie możesz o to poprosić

var partName = "part1.name";

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

var part1name = someObject[partBits[0]][partBits[1]];

Myślę, że OP prosi o ostatnie rozwiązanie. Jednak łańcuchy nie mają Splitmetody, ale raczej split.
duri

Właściwie pytałem ostatni. Zmienna partName jest wypełniona ciągiem wskazującym strukturę klucza do wartości. Twoje rozwiązanie wydaje się mieć sens. Jednak może być konieczne zmodyfikowanie w celu zwiększenia głębokości danych, takich jak poziom 4-5 i więcej. Zastanawiam się, czy mogę w ten sposób równomiernie traktować tablicę i obiekt?
Komaruloh

7

Tutaj oferuję więcej sposobów, które pod wieloma względami wydają się szybsze:

Opcja 1: Podziel ciąg na. lub [lub] lub „lub”, odwróć, pomiń puste elementy.

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

Opcja 2 (najszybsza ze wszystkich, z wyjątkiem eval): skanowanie znaków niskiego poziomu (brak wyrażeń regularnych / split / etc, tylko szybki skan znaków). Uwaga: Ten nie obsługuje cudzysłowów dla indeksów.

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)

Opcja 3: ( nowa : opcja 2 rozszerzona o obsługę cytatów - nieco wolniej, ale nadal szybko)

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 (...)” jest jednak nadal królem (pod względem wydajności). Jeśli masz ścieżki właściwości bezpośrednio pod kontrolą, nie powinno być żadnych problemów z używaniem „eval” (szczególnie, jeśli pożądana jest prędkość). Jeśli ciągnąc ścieżki własności „nad drutem” ( na linii !? Lol: P), to tak, użyj czegoś innego, aby być bezpiecznym. Tylko idiota powiedziałby, aby w ogóle nigdy nie używać „ewaluacji”, ponieważ istnieją dobre powody , aby z niego korzystać. Ponadto: „Jest używany w parserze JSON Douga Crockforda ”. Jeśli wejście jest bezpieczne, nie ma żadnych problemów. Użyj odpowiedniego narzędzia do właściwej pracy, to wszystko.


6

Na wszelki wypadek, gdy ktoś odwiedza to pytanie w 2017 roku lub później i szuka łatwego do zapamiętania sposobu, oto skomplikowany post na blogu na temat uzyskiwania dostępu do obiektów zagnieżdżonych w JavaScript bez przeszkadzania przez

Nie można odczytać właściwości „foo” niezdefiniowanego błędu

Uzyskaj dostęp do zagnieżdżonych obiektów za pomocą funkcji Array Reduce

Weźmy tę przykładową strukturę

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

Aby mieć dostęp do zagnieżdżonych tablic, możesz napisać własną tablicę, zmniejsz wykorzystanie.

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.

Istnieje również doskonały typ obsługujący minimalne typy bibliotek, który robi to wszystko za Ciebie.

W przypadku typów Twój kod będzie wyglądał następująco

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

Uwaga: Jestem autorem tego pakietu.


6

AngularJS

Podejście Speigga jest bardzo schludne i czyste, chociaż znalazłem tę odpowiedź, szukając rozwiązania dostępu do właściwości zakresu AngularJS $ po ścieżce łańcucha i przy niewielkiej modyfikacji wykonuje to zadanie:

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

Po prostu umieść tę funkcję w kontrolerze głównym i użyj dowolnego podrzędnego zakresu:

$scope.resolve( 'path.to.any.object.in.scope')

Zobacz AngularJS ma$scope.$eval inny sposób na zrobienie tego z AngularJS.
georgeawg

3

Nie znalazłem jeszcze pakietu do wykonania wszystkich operacji ze ścieżką łańcuchową, więc skończyłem pisać własny szybki pakiet, który obsługuje insert (), get () (z domyślnym zwrotem), set () i remove ( ) operacji.

Możesz używać notacji kropkowej, nawiasów, indeksów liczb, właściwości numerów i kluczy ze znakami innymi niż słowo. Proste użycie poniżej:

> var jsocrud = require('jsocrud');

...

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

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

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

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


3
/**
 * Access a deep value inside a object 
 * Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
 * @author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
 * Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
 */
function getDeepVal(obj, path) {
    if (typeof obj === "undefined" || obj === null) return;
    path = path.split(/[\.\[\]\"\']{1,2}/);
    for (var i = 0, l = path.length; i < l; i++) {
        if (path[i] === "") continue;
        obj = obj[path[i]];
        if (typeof obj === "undefined" || obj === null) return;
    }
    return obj;
}

Pracuje z

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

3

Prosta funkcja, pozwalająca na użycie ciągu znaków lub ścieżki tablicy.

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

LUB

console.log(get(obj, ['a', 'b', 'c'])); //foo

Jeśli masz zamiar napisać kod jako odpowiedź, wyjaśnij, dlaczego kod odpowiada na pytanie.
Tieson T.


2

Chociaż redukcja jest dobra, jestem zaskoczony, że nikt nie używał

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

Test


Nie sprawdzasz nawet, czy obiekt [klucz] faktycznie istnieje. To niewiarygodne.
Carles Alcolea,

@CarlesAlcolea domyślnie js nie sprawdzi, czy istnieje klucz obiektu: a.b.czgłosi wyjątek, jeśli obiekt nie ma właściwości b. Jeśli potrzebujesz czegoś po cichu odrzucając niewłaściwy keypath (którego nie polecam), nadal możesz zastąpić forEach tymkeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken

Przebiegam przez to obiekt, w którym brakowało nawiasów klamrowych, mój zły :)
Carles Alcolea

1

Niedawno miałem to samo pytanie i z powodzeniem zastosowałem https://npmjs.org/package/tea-properties, które również setzagnieżdżały obiekt / tablice:

dostać:

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

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

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

zestaw:

var o = {};

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

assert(o.prop.arr[0].foo, 'bar'); // true

„Ten moduł został wycofany. Użyj chaijs / pathval.”
Patrick Fisher

1

Zainspirowany odpowiedzią @ webjay: https://stackoverflow.com/a/46008856/4110122

Zrobiłem tę funkcję, której można użyć do uzyskania / ustawienia / rozbrojenia dowolnej wartości w obiekcie

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

Aby go użyć:

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

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

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

1

Rozwijam sklep internetowy z React. Próbowałem zmienić wartości w skopiowanym obiekcie stanu, aby zaktualizować pierwotny stan przy przesyłaniu. Powyższe przykłady nie działały dla mnie, ponieważ większość z nich mutuje strukturę kopiowanego obiektu. Znalazłem działający przykład funkcji dostępu i zmiany wartości właściwości głęboko zagnieżdżonego obiektu: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Oto:

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

1

Na podstawie odpowiedzi Alnitaka .

Owinęłam poliolek w kratkę i zredukowałem tę funkcję do pojedynczej redukcji łańcuchowej.

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

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

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


0

Jeśli chcesz uzyskać dostęp do innego zagnieżdżonego klucza, nie wiedząc o tym w czasie kodowania (adresowanie go będzie trywialne), możesz użyć akcesorium notacji tablicowej:

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

Odpowiadają one akcesorium notacji kropkowej i mogą się różnić w czasie wykonywania, na przykład:

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

var part1name = someObject[part][property];

jest równa

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

lub

var part1name = someObject.part1.name;

Mam nadzieję, że rozwiąże to twoje pytanie ...

EDYTOWAĆ

Nie będę używać ciągu znaków do zarządzania rodzajem zapytania xpath w celu uzyskania dostępu do wartości obiektu. Ponieważ musisz wywołać funkcję, aby przeanalizować zapytanie i pobrać wartość, wybrałbym inną ścieżkę (nie:

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

lub jeśli nie masz pewności co do metody zastosuj

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

Funkcje są krótsze, wyraźniejsze, interpreter sprawdza je pod kątem błędów składniowych i tak dalej.

Nawiasem mówiąc, czuję, że proste zadanie wykonane we właściwym czasie będzie wystarczające ...


Ciekawy. Ale w moim przypadku someObject jest już inicjowany, kiedy przypisuję wartość do part1name. Znam tylko strukturę. Dlatego używam łańcucha do opisania struktury. I mam nadzieję, że będę mógł użyć go do zapytania moich danych z someObject. Dziękujemy za podzielenie się swoją opinią. :)
Komaruloh

@Komaruloh: Myślę, że napisałbyś, że obiekt NIE został jeszcze zainicjowany podczas tworzenia zmiennych. Nawiasem mówiąc, nie rozumiem, dlaczego nie możesz wykonać zadania w odpowiednim czasie?
Eineki

Przepraszam, że nie wspomniałem, że someObject nie jest jeszcze zainicjowany. Z tego powodu someObject jest pobierany za pośrednictwem usługi internetowej. I chcę mieć tablicę nagłówka, która składa się z part1name, part2qty itp. Aby móc po prostu zapętlić tablicę nagłówków i uzyskać żądaną wartość na podstawie wartości part1name jako „klucz” / ścieżkę do someObject.
Komaruloh

0

Rozwiązania tutaj są tylko dla dostępu do głęboko zagnieżdżonych kluczy. Potrzebowałem jednego do uzyskiwania dostępu, dodawania, modyfikowania i usuwania kluczy. Oto co wymyśliłem:

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: ścieżka w tablicy. Możesz go zastąpić swoim string_path.split(".").
  • type_of_function: 0 za dostęp (nie przekazuj żadnej wartości value), 0 za dodawanie i modyfikowanie. 1 do usunięcia.

0

Opierając się na odpowiedzi Alnitaka:

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

}

Pozwala to również ustawić wartość!

I utworzeniu pakietu npm i GitHub z tego, jak dobrze


0

Zamiast ciągu można zastosować tablicę adresującą zagnieżdżone obiekty i tablice, np .: ["my_field", "another_field", 0, "last_field", 10]

Oto przykład, który zmieniłby pole w oparciu o tę reprezentację tablicy. Korzystam z czegoś takiego w React.js dla kontrolowanych pól wejściowych, które zmieniają stan zagnieżdżonych struktur.

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

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

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

0

Na podstawie poprzedniej odpowiedzi stworzyłem funkcję, która może również obsługiwać nawiasy. Ale nie ma w nich kropek z powodu podziału.

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

0

// (IE9+) Two steps

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

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

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

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


0

Praca z Underscore„s propertylub 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>

Powodzenia...

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.