Zugriff auf verschachtelte JavaScript-Objekte und -Arays über einen Zeichenfolgenpfad


454

Ich habe eine Datenstruktur wie diese:

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

Und ich möchte mit dieser Variablen auf die Daten zugreifen:

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

part1name sollte mit someObject.part1.namedem Wert "Part 1" gefüllt werden . Gleiches gilt für part2quantity, das mit 60 gefüllt ist.

Gibt es überhaupt eine Möglichkeit, dies mit reinem Javascript oder JQuery zu erreichen?


Nicht sicher, was Sie hier fragen? Sie möchten in der Lage sein, part1.name abzufragen und den Text "part1.name" zurückgeben zu lassen? Oder möchten Sie ein Mittel, um den Wert in part1.name zu speichern?
BonyT

Haben Sie versucht , zu tun wie var part1name = someObject.part1name;`
Rafay

1
@BonyT: Ich möchte someObject.part1.name abfragen und den Wert davon zurückgeben ("Part 1"). Ich möchte jedoch, dass die Abfrage (ich nannte sie "den Schlüssel") in einer Variablen 'part1name' gespeichert wird. Danke für deine Antwort. @ 3nigma: Das habe ich sicher getan. Das ist aber nicht meine Absicht. Danke für die Antwort.
Komaruloh

1
in der doppelten Antwort, ich liebe fyrs
Steve Black

Antworten:


520

Ich habe dies nur basierend auf einem ähnlichen Code gemacht, den ich bereits hatte. Es scheint zu funktionieren:

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

Verwendungszweck::

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

Eine funktionierende Demo finden Sie unter http://jsfiddle.net/alnitak/hEsys/.

BEARBEITEN Einige haben festgestellt, dass dieser Code einen Fehler auslöst, wenn eine Zeichenfolge übergeben wird, bei der die am weitesten links stehenden Indizes keinem korrekt verschachtelten Eintrag im Objekt entsprechen. Dies ist ein berechtigtes Anliegen, aber IMHO wird try / catchbeim Aufrufen am besten mit einem Block angesprochen , anstatt diese Funktion stillschweigend undefinedfür einen ungültigen Index zurückgeben zu lassen.


19
Das funktioniert wunderbar. Bitte tragen Sie dies zum Internet bei, indem Sie es als Knotenpaket verpacken.
t3dodson

14
@ t3dodson Ich habe es gerade getan: github.com/capaj/object-resolve-path Beachten Sie nur, dass dies nicht gut funktioniert, wenn Ihr Eigenschaftsname '[]' an sich enthält. Regex ersetzt es durch '.' und es funktioniert nicht wie erwartet
Capaj

20
tolles Zeug; Mit der Lodash-Bibliothek kann man auch:_.get(object, nestedPropertyString);
ian

17
Dies geht wahrscheinlich im Meer der Kommentare verloren, es tritt jedoch ein Fehler auf, wenn Sie versuchen, eine nicht vorhandene Eigenschaft zu adressieren. Also 'part3[0].name.iDontExist'. Durch Hinzufügen einer Überprüfung, ob oein Objekt in der if inDatei enthalten ist, wird das Problem behoben. (Wie Sie das machen, liegt bei Ihnen). Siehe aktualisierte Geige: jsfiddle.net/hEsys/418
ste2425

2
Das ist so Gold. Wir haben eine konfigurationsbasierte Anwendung und das ist irgendwie hilfreich! Vielen Dank!
Christian Esperar

182

Dies ist die Lösung, die ich benutze:

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

Anwendungsbeispiel:

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

Einschränkungen:

  • Brackets ( []) können nicht für Array-Indizes verwendet werden. Die Angabe von Array-Indizes zwischen dem Trennzeichen (z. B. .) funktioniert jedoch wie oben gezeigt.

7
Die Verwendung _.reduce()von Reduce ist eine hervorragende Lösung (man kann sie auch aus der Unterstrich- oder Lodash-Bibliothek verwenden)
Alp

3
Ich denke selfist hier wahrscheinlich undefiniert. Meinst du this?
Platinum Azure

2
Hier ist meine Ergänzung eingestellten Werte durch Pfad: pastebin.com/jDp5sKT9
mroach

1
Weiß jemand, wie man dies auf TypeScript portiert?
Adam Plocher

1
@ SC1000 gute Idee. Diese Antwort wurde geschrieben, bevor Standardparameter in den meisten Browsern verfügbar waren. Ich werde es auf "Funktionsauflösung (Pfad, obj = Selbst)" aktualisieren, da das Verweisen auf das globale Objekt als Standard beabsichtigt ist.
Speigg

179

Dies wird jetzt von lodash using unterstützt _.get(obj, property). Siehe https://lodash.com/docs#get

Beispiel aus den Dokumenten:

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
Dies sollte die einzig akzeptierte Antwort sein, da dies die einzige ist, die sowohl für die Punkt- als auch für die Klammer-Syntax funktioniert. Sie schlägt nicht fehl, wenn wir '[]' in der Zeichenfolge eines Schlüssels im Pfad haben.
Capaj

7
Diese. Plus, es unterstützt_.set(...)
Josh C.

Was passiert, wenn das Objekt nicht gefunden wird?
DDave

@DDave zeigt, wenn der als Objekt übergebene Wert undefiniert oder kein Objekt ist, _.getdasselbe Verhalten wie wenn im angegebenen Objekt kein Schlüssel gefunden wird. zB _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Dieses Verhalten ist jedoch in den Dokumenten nicht definiert und kann sich daher ändern.
Ian Walker-Sperber

5
@ Capaj du machst Witze? Und wer will / kann nicht lodash benutzen?
Andre Figueiredo

74

ES6 : Nur eine Zeile in Vanila JS (sie gibt null zurück, wenn sie nicht gefunden wird, anstatt einen Fehler zu geben):

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

Oder Beispiel:

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

Für eine gebrauchsfertige Funktion, die auch falsche, 0 und negative Zahlen erkennt und Standardwerte als Parameter akzeptiert:

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

Zu verwendendes Beispiel:

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 :

Um setzen einen Pfad (Angefordert von @ rob-gordon) können Sie verwenden:

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

Beispiel:

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

Zugriff auf Array mit [] :

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

Beispiel:

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

2
Ich liebe diese Technik. Das ist wirklich chaotisch, aber ich wollte diese Technik für die Zuordnung verwenden. 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
Ich mag die Idee, Reduce zu verwenden, aber Ihre Logik scheint nicht zu stimmen 0, undefinedund nullWerte. {a:{b:{c:0}}}kehrt nullstatt statt 0. Möglicherweise werden diese Probleme durch explizites Überprüfen auf null oder undefiniert behoben. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku

Hallo @SmujMaiku, die "Ready to Use" -Funktion gibt korrekt für '0', 'undefined' und 'null' zurück. Ich habe gerade auf der Konsole getestet: resolvePath ({a: {b: {c: 0}}}, ' abc ', null) => 0; Es prüft, ob der Schlüssel anstelle des Wertes selbst existiert, wodurch mehr als eine Prüfung
vermieden wird

hier hat defaultValue nicht funktioniert, obwohl Reflect.has(o, k) ? ...(ES6 Reflect.has ) funktioniert hat
Andre Figueiredo

setPathsetzt den Wert beim ersten Auftreten des letzten Selektors. Zum Beispiel let o = {}; setPath(o, 'a.a', 0)führt {a: 0}eher zu als {a: {a: 0}}. In diesem Beitrag finden Sie eine Lösung.
pkfm

62

Sie müssten die Zeichenfolge selbst analysieren:

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

Dazu müssen Sie auch Array-Indizes mit Punktnotation definieren:

var part3name1 = "part3.0.name";

Dies erleichtert das Parsen.

DEMO


@ Felix Kling: Ihre Lösung bietet mir das, was ich brauche. Und ich danke Ihnen vielmals dafür. Aber Alnitak bietet auch verschiedene Möglichkeiten und scheint auch zu funktionieren. Da ich nur eine Antwort auswählen kann, werde ich die Antwort von Alnitak wählen. Nicht dass seine Lösung besser wäre als du oder so. Wie auch immer, ich schätze Ihre Lösung und Ihren Einsatz sehr.
Komaruloh

1
@Komaruloh: Oh ich dachte du kannst immer Antworten auf deine eigene Frage abstimmen .... sowieso war ich mehr oder weniger ein Scherz, ich brauche keinen besseren Ruf;) Happy Coding!
Felix Kling

1
@ Felix Kling: Du brauchst mindestens 15 Reputationen, um abstimmen zu können. :) Ich glaube, du brauchst mit 69k + keinen besseren Ruf. Danke
Komaruloh

@Felix FWIW - Das Konvertieren von []Syntax in Eigenschaftssyntax ist ziemlich trivial.
Alnitak

4
Wenn Sie die while-Schleife in ändern, while (l > 0 && (obj = obj[current]) && i < l)funktioniert dieser Code auch für Zeichenfolgen ohne Punkte.
Snea

39

Funktioniert auch für Arrays / Arrays innerhalb des Objekts. Defensiv gegen ungültige Werte.

/**
 * 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
Vielen Dank, dies ist die beste und performanteste Antwort - jsfiddle.net/Jw8XB/1
Dominic

@Endless, ich möchte betonen, dass der Pfad die Elemente durch Punkte trennen sollte. Zahnspangen funktionieren nicht. Um auf das erste Element im Array zuzugreifen, verwenden Sie "0.sp ace".
TheZver

26

mit eval:

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

Wrap, um bei einem Fehler undefiniert zurückzugeben

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

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

Bitte seien Sie mit gesundem Menschenverstand und Vorsicht, wenn Sie die Macht der Bewertung ausüben. Es ist ein bisschen wie ein Lichtschwert. Wenn Sie es einschalten, besteht eine Wahrscheinlichkeit von 90%, dass Sie sich ein Glied abtrennen. Es ist nicht jedermanns Sache.


7
Ob eval eine gute Idee ist oder nicht, hängt davon ab, woher die Eigenschaftszeichenfolgendaten stammen. Ich bezweifle, dass Sie Grund zur Sorge haben, dass Hacker über ein statisches "var p = 'abc'; eval (p);" Geben Sie call ein. Dafür ist es eine sehr gute Idee.
James Wilkins

17

Mit dem folgenden einfachen Trick können Sie den Wert eines Deep-Object-Elements mit Punktnotation ohne externe JavaScript-Bibliothek ermitteln:

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

In Ihrem Fall, um den Wert part1.namevon someObjecteinfach zu erhalten, tun Sie Folgendes:

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

Hier ist eine einfache Geigen-Demo: https://jsfiddle.net/harishanchu/oq5esowf/


3
Funktion deep_value (obj, Pfad) {neue Funktion zurückgeben ('o', 'o zurückgeben' + Pfad) (obj); }
ArcangelZith

14

Dies wird wahrscheinlich nie das Licht der Welt erblicken ... aber hier ist es trotzdem.

  1. Ersetzen [] Klammernsyntax durch.
  2. Split auf .Zeichen
  3. Entfernen Sie leere Zeichenfolgen
  4. Finde den Pfad (sonst undefined)

// "one liner" (ES6)

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

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

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


11

Es ist ein Einzeiler mit 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"

Oder noch besser ...

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

Oder ES6-Version mit ...

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

Plunkr


7

Ich denke, Sie fragen danach:

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

Sie könnten danach fragen:

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

Beides wird funktionieren


Oder vielleicht fragen Sie danach

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

var part1name = someObject[partName][nameStr];

Schließlich könnten Sie danach fragen

var partName = "part1.name";

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

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

Ich denke, OP fragt nach der letzten Lösung. Strings haben jedoch keine SplitMethode, sondern split.
Duri

Eigentlich habe ich den letzten gefragt. Die Variable partName ist mit einer Zeichenfolge gefüllt, die die zu bewertende Schlüsselstruktur angibt. Ihre Lösung scheint sinnvoll zu sein. Möglicherweise muss ich jedoch Änderungen vornehmen, um die Tiefe der Daten zu erhöhen, z. B. Stufe 4-5 und mehr. Und ich frage mich, ob ich das Array und das Objekt damit einheitlich behandeln kann?
Komaruloh

7

Hier biete ich mehr Möglichkeiten an, die in vielerlei Hinsicht schneller erscheinen:

Option 1: String aufteilen. oder [oder] oder 'oder ", kehren Sie es um, überspringen Sie leere Elemente.

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

Option 2 (außer am schnellsten eval): Niedriger Zeichenscan (kein Regex / Split / etc, nur ein schneller Char-Scan). Hinweis: Dieser unterstützt keine Anführungszeichen für Indizes.

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)

Option 3: ( neu : Option 2 erweitert, um Anführungszeichen zu unterstützen - etwas langsamer, aber immer noch schnell)

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 (...)" ist immer noch König (was die Leistung betrifft). Wenn Sie Eigenschaftspfade direkt unter Ihrer Kontrolle haben, sollte es keine Probleme mit der Verwendung von 'eval' geben (insbesondere wenn Geschwindigkeit gewünscht wird). Wenn Sie Eigenschaftspfade "über den Draht" ziehen ( auf der Linie !? Lol: P), verwenden Sie aus Sicherheitsgründen etwas anderes. Nur ein Idiot würde sagen, dass er "eval" niemals verwenden soll, da es gute Gründe gibt, es zu verwenden. Außerdem "wird es im JSON-Parser von Doug Crockford verwendet ." Wenn die Eingabe sicher ist, gibt es überhaupt keine Probleme. Verwenden Sie das richtige Werkzeug für den richtigen Job, das war's.


6

Für den Fall, dass jemand diese Frage 2017 oder später besucht und nach einer leicht zu merkenden Möglichkeit sucht , finden Sie hier einen ausführlichen Blog-Beitrag zum Zugriff auf verschachtelte Objekte in JavaScript ohne von ihm verwirrt zu werden

Die Eigenschaft 'foo' von undefined kann nicht gelesen werden Fehlers werden

Greifen Sie mit Array Reduce auf verschachtelte Objekte zu

Nehmen wir diese Beispielstruktur

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

Um auf verschachtelte Arrays zugreifen zu können, können Sie Ihr eigenes Array schreiben.

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.

Es gibt auch eine ausgezeichnete Schrift, die minimale Bibliotheksfehler verarbeitet , der all dies für Sie erledigt.

Mit typy sieht Ihr Code folgendermaßen aus

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

Haftungsausschluss: Ich bin der Autor dieses Pakets.


6

AngularJS

Speiggs Ansatz ist sehr ordentlich und sauber, obwohl ich diese Antwort gefunden habe, als ich nach der Lösung gesucht habe, auf AngularJS $ scope-Eigenschaften über einen Zeichenfolgenpfad zuzugreifen, und mit einer kleinen Änderung die Aufgabe erfüllt:

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

Platzieren Sie diese Funktion einfach in Ihrem Root-Controller und verwenden Sie einen beliebigen untergeordneten Bereich wie folgt:

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

Siehe AngularJS bietet$scope.$eval eine andere Möglichkeit, dies mit AngularJS zu tun.
Georgeawg

3

Ich habe noch kein Paket gefunden, um alle Operationen mit einem Zeichenfolgenpfad auszuführen. Daher habe ich mein eigenes schnelles kleines Paket geschrieben, das insert (), get () (mit Standardrückgabe), set () und remove ( ) Operationen.

Sie können Punktnotation, Klammern, Zahlenindizes, Eigenschaften von Zeichenfolgenummern und Schlüssel mit Nicht-Wort-Zeichen verwenden. Einfache Verwendung unten:

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

Arbeitet mit

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

Einfache Funktion, die entweder einen String- oder einen Array-Pfad zulässt.

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

ODER

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

Wenn Sie Code als Antwort veröffentlichen möchten, erklären Sie bitte, warum der Code die Frage beantwortet.
Tieson T.


2

Obwohl Reduzieren gut ist, bin ich überrascht, dass niemand für jedes verwendet hat:

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

Prüfung


Sie überprüfen nicht einmal, ob obj [Schlüssel] tatsächlich existiert. Es ist unzuverlässig.
Carles Alcolea

@CarlesAlcolea Standardmäßig prüft js weder, ob der Schlüssel eines Objekts vorhanden ist, noch löst es a.b.ceine Ausnahme aus, wenn bIhr Objekt keine Eigenschaft enthält . Wenn Sie etwas brauchen, das den falschen Schlüsselpfad stillschweigend abweist (was ich nicht empfehle), können Sie den forEach trotzdem durch diesen ersetzenkeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken

Ich laufe durch ein Objekt, dem eine geschweifte Klammer fehlte, mein schlechtes :)
Carles Alcolea

1

Hatte gerade die gleiche Frage vor kurzem und erfolgreich verwendet https://npmjs.org/package/tea-properties, die auchset Objekt / Arrays verschachtelt:

erhalten:

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

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

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

einstellen:

var o = {};

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

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

"Dieses Modul wurde eingestellt. Verwenden Sie chaijs / pathval."
Patrick Fisher

1

Inspiriert von der Antwort von @ webjay: https://stackoverflow.com/a/46008856/4110122

Ich habe diese Funktion erstellt, mit der Sie einen beliebigen Wert im Objekt abrufen / setzen / deaktivieren können

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

Um es zu benutzen:

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

Ich entwickle einen Online-Shop mit React. Ich habe versucht, die Werte im kopierten Statusobjekt zu ändern, um den ursprünglichen Status beim Senden zu aktualisieren. Die obigen Beispiele haben bei mir nicht funktioniert, da die meisten von ihnen die Struktur des kopierten Objekts mutieren. Ich habe ein funktionierendes Beispiel für die Funktion zum Zugreifen auf und Ändern von Werten der tief verschachtelten Objekteigenschaften gefunden: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Hier ist es:

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

Basierend auf Alnitaks Antwort .

Ich wickelte die Polyfüllung in einen Scheck ein und reduzierte die Funktion auf eine einzelne verkettete Reduktion.

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

Wenn Sie auf verschiedene verschachtelte Schlüssel zugreifen müssen, ohne diese zum Zeitpunkt der Codierung zu kennen (es ist trivial, sie zu adressieren), können Sie den Array-Notations-Accessor verwenden:

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

Sie entsprechen dem Punktnotations-Accessor und können zur Laufzeit variieren, zum Beispiel:

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

var part1name = someObject[part][property];

ist äquivalent zu

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

oder

var part1name = someObject.part1.name;

Ich hoffe das adressiert deine Frage ...

BEARBEITEN

Ich werde keine Zeichenfolge verwenden, um eine Art xpath- Abfrage für den Zugriff auf einen Objektwert zu verwalten. Da Sie eine Funktion aufrufen müssen, um die Abfrage zu analysieren und den Wert abzurufen, würde ich einem anderen Pfad folgen (nicht:

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

oder, wenn Sie mit der Apply- Methode nicht zufrieden sind

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

Die Funktionen sind kürzer, klarer, der Interpreter überprüft sie für Sie auf Syntaxfehler und so weiter.

Übrigens denke ich, dass eine einfache Aufgabe, die zum richtigen Zeitpunkt gemacht wird, ausreichen wird ...


Interessant. In meinem Fall wird das someObject jedoch noch initialisiert, wenn ich part1name einen Wert zuweise. Ich kenne nur die Struktur. Deshalb benutze ich einen String, um die Struktur zu beschreiben. Und in der Hoffnung, damit meine Daten von someObject abfragen zu können. Vielen Dank für Ihre Meinung. :)
Komaruloh

@Komaruloh: Ich denke, Sie würden schreiben, dass das Objekt noch NICHT initialisiert ist, wenn Sie Ihre Variablen erstellen. Übrigens verstehe ich nicht, warum können Sie die Aufgabe nicht zu gegebener Zeit erledigen?
Eineki

Es tut uns leid, dass nicht erwähnt wurde, dass someObject noch nicht initialisiert ist. Aus diesem Grund wird someObject über einen Webdienst abgerufen. Und ich möchte ein Array von Headern haben, die aus part1name, part2qty usw. bestehen. Damit ich einfach das Header-Array durchlaufen und den gewünschten Wert basierend auf part1name als 'Schlüssel' / Pfad zu someObject erhalten kann.
Komaruloh

0

Die Lösungen hier sind nur für den Zugriff auf die tief verschachtelten Schlüssel. Ich brauchte einen, um auf die Schlüssel zuzugreifen, sie hinzuzufügen, zu ändern und zu löschen. Das habe ich mir ausgedacht:

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: Pfad in einem Array. Sie können es durch Ihr ersetzen string_path.split(".").
  • type_of_function: 0 für den Zugriff (geben Sie keinen Wert an value), 0 für das Hinzufügen und Ändern. 1 zum Löschen.

0

Aufbauend auf Alnitaks Antwort:

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

}}

Damit können Sie auch einen Wert einstellen!

Ich habe ein erstellt npm Paket und Github mit diesem als auch


0

Anstelle einer Zeichenfolge kann ein Array verwendet werden, um verschachtelte Objekte und Arrays zu adressieren, z. ["my_field", "another_field", 0, "last_field", 10]

Hier ist ein Beispiel, das ein Feld basierend auf dieser Array-Darstellung ändern würde. Ich verwende so etwas in react.js für kontrollierte Eingabefelder, die den Status verschachtelter Strukturen ändern.

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

Basierend auf einer früheren Antwort habe ich eine Funktion erstellt, die auch Klammern verarbeiten kann. Aber keine Punkte in ihnen aufgrund der Spaltung.

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

Arbeiten mit Underscore's propertyoder 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>

Viel Glück...

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.