Konvertieren Sie eine JavaScript-Zeichenfolge in Punktnotation in eine Objektreferenz

214

Gegeben ein JS-Objekt

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

und eine Schnur

"a.b"

Wie kann ich die Zeichenfolge in Punktnotation konvertieren, damit ich gehen kann?

var val = obj.a.b

Wenn die Zeichenfolge nur wäre 'a', könnte ich verwenden obj[a]. Das ist aber komplexer. Ich stelle mir vor, dass es eine einfache Methode gibt, die aber derzeit entgeht.

nevf
quelle
25
@ Andrerey evalist böse; benutze es nicht
Kevinji
5
Zu Ihrer Information
James Wilkins
Wenn perf eine ernsthafte Überlegung ist und Sie häufig dieselben Pfade wiederverwenden (z. B. innerhalb einer Array-Filterfunktion), verwenden Sie den Funktionskonstruktor, wie in meiner Antwort unten beschrieben. Wenn derselbe Pfad tausende Male verwendet wird, kann die Funktionsmethode mehr als zehnmal so schnell sein wie das Auswerten oder Aufteilen und Reduzieren des Pfads bei jeder Dereferenzierung.
Kevin Crumley

Antworten:

436

Anmerkung der letzten Zeit: Obwohl ich geschmeichelt bin, dass diese Antwort viele positive Stimmen erhalten hat, bin ich auch etwas entsetzt. Wenn man Punktnotationszeichenfolgen wie "xabc" in Referenzen konvertieren muss, könnte dies (vielleicht) ein Zeichen dafür sein, dass etwas sehr falsch ist (es sei denn, Sie führen eine seltsame Deserialisierung durch).

Das heißt, Anfänger, die den Weg zu dieser Antwort finden, müssen sich die Frage stellen: "Warum mache ich das?"

Es ist natürlich im Allgemeinen in Ordnung, dies zu tun, wenn Ihr Anwendungsfall klein ist und Sie nicht auf Leistungsprobleme stoßen, UND Sie müssen nicht auf Ihrer Abstraktion aufbauen, um sie später komplizierter zu machen. Wenn dies die Komplexität des Codes verringert und die Dinge einfach hält, sollten Sie wahrscheinlich das tun, was OP verlangt. Wenn dies jedoch nicht der Fall ist, prüfen Sie, ob eine der folgenden Bedingungen erfüllt ist:

Fall 1 : Als primäre Methode zum Arbeiten mit Ihren Daten (z. B. als Standardform Ihrer App zum Weitergeben und Dereferenzieren von Objekten). Zum Beispiel die Frage "Wie kann ich einen Funktions- oder Variablennamen aus einer Zeichenfolge nachschlagen?".

  • Dies ist eine schlechte Programmierpraxis (unnötige Metaprogrammierung speziell und verstößt gegen den funktionsnebenwirkungsfreien Codierungsstil und führt zu Leistungseinbußen). Anfänger, die sich in diesem Fall befinden, sollten stattdessen in Betracht ziehen, mit Array-Darstellungen zu arbeiten, z. B. ['x', 'a', 'b', 'c'] oder wenn möglich sogar direkter / einfacher / unkomplizierter: wie nicht zu verlieren Verfolgen Sie in erster Linie die Referenzen selbst (am besten, wenn sie nur clientseitig oder nur serverseitig sind) usw. (Eine bereits vorhandene eindeutige ID wäre unelegant zum Hinzufügen, könnte jedoch verwendet werden, wenn die Spezifikation dies anderweitig erfordert Existenz unabhängig.)

Fall 2 : Arbeiten mit serialisierten Daten oder Daten, die dem Benutzer angezeigt werden. Verwenden Sie beispielsweise ein Datum als Zeichenfolge "1999-12-30" anstelle eines Datumsobjekts (was zu Zeitzonenfehlern führen oder die Komplexität der Serialisierung erhöhen kann, wenn Sie nicht vorsichtig sind). Oder Sie wissen, was Sie tun.

  • Das ist vielleicht in Ordnung. Achten Sie darauf, dass keine Punktzeichenfolgen "" vorhanden sind. in Ihren bereinigten Eingabefragmenten.

Wenn Sie diese Antwort ständig verwenden und zwischen Zeichenfolge und Array hin und her konvertieren, sind Sie möglicherweise im schlechten Fall und sollten eine Alternative in Betracht ziehen.

Hier ist ein eleganter Einzeiler, der 10x kürzer ist als die anderen Lösungen:

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

[Bearbeiten] Oder in ECMAScript 6:

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

(Nicht, dass ich denke, dass eval immer schlecht ist, wie andere vermuten, dass es ist (obwohl es normalerweise so ist), dennoch werden sich diese Leute freuen, dass diese Methode eval nicht verwendet. Die oben genannten finden obj.a.b.etcgegeben objund die Zeichenfolge "a.b.etc".)

Als Antwort auf diejenigen, die immer noch Angst vor der Verwendung haben, reduceobwohl sie im ECMA-262-Standard (5. Ausgabe) enthalten sind, gibt es hier eine zweizeilige rekursive Implementierung:

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

Abhängig von den Optimierungen, die der JS-Compiler vornimmt, möchten Sie möglicherweise sicherstellen, dass verschachtelte Funktionen nicht bei jedem Aufruf über die üblichen Methoden neu definiert werden (Platzieren in einem Abschluss, Objekt oder globalen Namespace).

bearbeiten :

Um eine interessante Frage in den Kommentaren zu beantworten:

Wie würden Sie daraus auch einen Setter machen? Die Werte nicht nur nach Pfad zurückgeben, sondern auch festlegen, wenn ein neuer Wert an die Funktion gesendet wird? - Swader 28. Juni um 21:42 Uhr

(Nebenbemerkung: Leider kann ein Objekt nicht mit einem Setter zurückgegeben werden, da dies gegen die aufrufende Konvention verstoßen würde. Der Kommentator scheint sich stattdessen auf eine allgemeine Setter-Funktion mit Nebenwirkungen wie " index(obj,"a.b.etc", value)Doing" zu beziehen obj.a.b.etc = value.)

Der reduceStil ist dafür nicht wirklich geeignet, aber wir können die rekursive Implementierung ändern:

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

Demo:

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

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

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

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

... obwohl ich persönlich empfehlen würde, eine separate Funktion zu erstellen setIndex(...). Ich möchte mit einer Randnotiz enden, dass der ursprüngliche Poser der Frage eher mit Arrays von Indizes (von denen sie stammen können .split) als mit Strings arbeiten könnte (sollte?) ; obwohl an einer Komfortfunktion normalerweise nichts auszusetzen ist.


Ein Kommentator fragte:

Was ist mit Arrays? so etwas wie "ab [4] .cd [1] [2] [3]"? –AlexS

Javascript ist eine sehr seltsame Sprache; Im Allgemeinen können Objekte nur Zeichenfolgen als Eigenschaftsschlüssel haben. Wenn xes sich beispielsweise um ein generisches Objekt handelt x={}, wird x[1]dies zu x["1"]... Sie lesen das richtig ... yup ...

Javascript-Arrays (die selbst Instanzen von Object sind) fördern speziell Ganzzahlschlüssel, obwohl Sie so etwas tun könnten x=[]; x["puppy"]=5;.

Aber im Allgemeinen (und es gibt Ausnahmen) x["somestring"]===x.somestring(wenn es erlaubt ist; können Sie nicht x.123).

(Denken Sie daran, dass jeder JS-Compiler, den Sie verwenden, diese möglicherweise zu vernünftigeren Darstellungen kompilieren kann, wenn dies beweisen kann, dass sie nicht gegen die Spezifikation verstoßen.)

Die Antwort auf Ihre Frage hängt also davon ab, ob Sie davon ausgehen, dass diese Objekte nur Ganzzahlen akzeptieren (aufgrund einer Einschränkung in Ihrer Problemdomäne) oder nicht. Nehmen wir nicht an. Dann ist ein gültiger Ausdruck eine Verkettung einer Basiskennung plus einiger .identifiers plus einiger ["stringindex"]s

Dies wäre dann gleichbedeutend mit a["b"][4]["c"]["d"][1][2][3], obwohl wir wahrscheinlich auch unterstützen sollten a.b["c\"validjsstringliteral"][3]. Sie müssten den Abschnitt zur Ecmascript-Grammatik für Zeichenfolgenliterale überprüfen, um zu sehen, wie ein gültiges Zeichenfolgenliteral analysiert wird. Technisch gesehen möchten Sie auch (anders als in meiner ersten Antwort) überprüfen, ob aes sich um eine gültige Javascript-Kennung handelt .

Eine einfache Antwort auf Ihre Frage aber, wenn die Saiten Kommas oder Klammern nicht enthalten , würde sein gerade Länge 1+ Sequenzen von Zeichen in der Menge nicht übereinstimmen ,oder [oder ]:

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

Wenn Ihre Zeichenfolgen keine Escape-Zeichen oder "Zeichen enthalten und IdentifierNames eine Subsprache von StringLiterals sind (glaube ich ???), können Sie Ihre Punkte zuerst in [] konvertieren:

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

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

Seien Sie natürlich immer vorsichtig und vertrauen Sie niemals Ihren Daten. Einige schlechte Methoden, die für einige Anwendungsfälle funktionieren könnten, umfassen auch:

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

Special 2018 bearbeiten:

Lassen Sie uns den Kreis schließen und die ineffizienteste, schrecklich übermetaprogrammierte Lösung finden, die wir finden können ... im Interesse der Hamfisterie mit syntaktischer Reinheit . Mit ES6-Proxy-Objekten! ... Definieren wir auch einige Eigenschaften, die (imho sind in Ordnung und wunderbar, aber) möglicherweise falsch geschriebene Bibliotheken beschädigen. Sie sollten vielleicht vorsichtig sein, wenn Sie sich um Leistung, geistige Gesundheit (Ihre oder die anderer), Ihren Job usw. kümmern.

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

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

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

Demo:

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

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

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

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

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

Ausgabe:

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

(Proxy Override get) objHyper ['abc'] ist: 1

(Proxy-Override-Set) objHyper ['abc'] = 3, jetzt ist obj: {"a": {"b": {"c": 3, "d": 2}}}

(hinter den Kulissen) objHyper ist: Proxy {a: {…}}

(Abkürzung) obj.H ['abc'] = 4

(Abkürzung) obj.H ['abc'] ist obj ['a'] ['b'] ['c'] ist: 4

ineffiziente Idee: Sie können das oben Gesagte basierend auf dem Eingabeargument so ändern, dass es versendet. Verwenden Sie entweder die .match(/[^\]\[.]+/g)Methode zur Unterstützung obj['keys'].like[3]['this']oder wenn instanceof Array, dann akzeptieren Sie einfach ein Array als Eingabe wie keys = ['a','b','c']; obj.H[keys].


Per Vorschlag, dass Sie möglicherweise undefinierte Indizes in einem "weicheren" NaN-Stil behandeln möchten (z. B. index({a:{b:{c:...}}}, 'a.x.c')undefinierte anstelle von nicht erfassten TypeError zurückgeben) ...:

1) Dies ist unter dem Gesichtspunkt "Wir sollten undefiniert zurückgeben, anstatt einen Fehler zu werfen" in der eindimensionalen Indexsituation ({}) ['eg'] == undefiniert sinnvoll. "Wir sollten also undefiniert zurückgeben, anstatt einen Fehler zu werfen." Fehler "in der N-dimensionalen Situation.

2) Dies ist aus der Perspektive, die wir machen , nicht sinnvoll x['a']['x']['c'], was mit einem TypeError im obigen Beispiel fehlschlagen würde.

Das heißt, Sie würden diese Arbeit machen, indem Sie Ihre Reduktionsfunktion durch Folgendes ersetzen:

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

(Sie können dies effizienter gestalten, indem Sie eine for-Schleife verwenden und abbrechen / zurückgeben, wenn das Unterergebnis, in das Sie als nächstes indizieren möchten, nicht definiert ist, oder einen Try-Catch verwenden, wenn Sie erwarten, dass solche Fehler ausreichend selten sind.)

Ninjagecko
quelle
4
reducewird nicht in allen derzeit verwendeten Browsern unterstützt.
Ricardo Tomasi
14
@ Ricardo: Array.reduceist Teil des ECMA-262-Standards. Wenn Sie wirklich veraltete Browser unterstützen möchten, können Sie Array.prototype.reducedie irgendwo angegebene Beispielimplementierung definieren (z . B. developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
Ninjagecko
2
Ja, aber es ist einfach genug, die beiden Zeilen in eine Funktion einzufügen. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
Nevf
3
Ich liebe dieses elegante Beispiel, danke Ninjagecko. Ich habe es erweitert, um Array-Stil-Notation sowie leere Zeichenfolgen zu behandeln - siehe mein Beispiel hier: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD
2
@ninjagecko wie würdest du das auch zu einem Setter machen? Die Werte nicht nur nach Pfad zurückgeben, sondern auch festlegen, wenn ein neuer Wert an die Funktion gesendet wird?
Swader
64

Wenn Sie lodash verwenden können , gibt es eine Funktion, die genau das tut:

_.get (Objekt, Pfad, [Standardwert])

var val = _.get(obj, "a.b");
Petar Ivanov
quelle
4
Hinweis: _.get(object, path)Wird nicht unterbrochen, wenn kein Pfad gefunden wurde. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)tut. Für meinen speziellen Fall - nicht für jeden Fall - genau das, was ich brauchte. Vielen Dank!
Herr B.
1
@ Mr.B. Die neueste Version von Lodash hat ein drittes optionales Argument für defaultValue. Die _.get()Methode gibt den Standardwert zurück, wenn er _.get()in aufgelöst undefinedwird. Setzen Sie ihn also auf den gewünschten Wert und achten Sie auf den von Ihnen festgelegten Wert.
Steampowered
7
Für alle, die sich fragen, unterstützt es auch _.set(object, path, value).
Jeffrey Roosendaal
19

Ein etwas komplizierteres Beispiel mit Rekursion.

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


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

alert(recompose(obj,'a.d.a.b')); //blah
Joekarl
quelle
19

Sie können auch lodash.get verwenden

Sie installieren einfach dieses Paket (npm i --save lodash.get) und verwenden es dann folgendermaßen:

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

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

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

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

Wenn Sie davon ausgehen, dass Sie denselben Pfad mehrmals dereferenzieren, hat das Erstellen einer Funktion für jeden Punktnotationspfad bei weitem die beste Leistung (Erweiterung der Perf-Tests, auf die James Wilkins in den obigen Kommentaren verwiesen hat).

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

Die Verwendung des Funktionskonstruktors hat einige der gleichen Nachteile wie eval () in Bezug auf Sicherheit und Worst-Case-Leistung, aber IMO ist ein schlecht genutztes Tool für Fälle, in denen Sie eine Kombination aus extremer Dynamik und hoher Leistung benötigen. Ich verwende diese Methode, um Array-Filterfunktionen zu erstellen und sie in einer AngularJS-Digest-Schleife aufzurufen. Meine Profile zeigen konsistent den Schritt array.filter (), der weniger als 1 ms benötigt, um etwa 2000 komplexe Objekte zu dereferenzieren und zu filtern, wobei dynamisch definierte Pfade mit einer Tiefe von 3-4 Ebenen verwendet werden.

Eine ähnliche Methodik könnte natürlich verwendet werden, um Setterfunktionen zu erstellen:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Kevin Crumley
quelle
1
Wenn Sie dieselben Pfade über einen längeren Zeitraum dereferenzieren müssen, zeigt der obige Link jsperf.com ein Beispiel für das Speichern und spätere Nachschlagen der Funktion. Das Aufrufen des Funktionskonstruktors ist ziemlich langsam, daher sollte High-Perf-Code die Ergebnisse speichern, um eine Wiederholung nach Möglichkeit zu vermeiden.
Kevin Crumley
9

Viele Jahre seit dem ursprünglichen Beitrag. Jetzt gibt es eine großartige Bibliothek namens 'Objektpfad'. https://github.com/mariocasciaro/object-path

Verfügbar auf NPM und BOWER https://www.npmjs.com/package/object-path

Es ist so einfach wie:

objectPath.get(obj, "a.c.1");  //returns "f"
objectPath.set(obj, "a.j.0.f", "m");

Und funktioniert für tief verschachtelte Eigenschaften und Arrays.

LPG
quelle
7

Ich schlage vor, den Pfad zu teilen und zu iterieren und das Objekt zu reduzieren, das Sie haben. Dieser Vorschlag arbeitet mit einem Standardwert für fehlende Eigenschaften.

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

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

Nina Scholz
quelle
6

Hinweis: Wenn Sie Lodash bereits verwenden, können Sie die Funktionen propertyoder getverwenden:

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

Unterstrich hat auch eine propertyFunktion, unterstützt jedoch keine Punktnotation.

Tamlyn
quelle
1
Der Unterstrich scheint die Punktnotation nicht zu unterstützen. Antwort aktualisiert.
Tamlyn
5

Andere Vorschläge sind etwas kryptisch, daher dachte ich, ich würde dazu beitragen:

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

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

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

Sie können die unter npm verfügbare Bibliothek verwenden, was diesen Vorgang vereinfacht. https://www.npmjs.com/package/dot-object

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

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

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

// Result: Hi there!
Jeremias Santos
quelle
1
Vielen Dank, dass Sie uns darauf aufmerksam gemacht haben. Sieht sehr nützlich aus.
Nevf
Und deshalb fügt jedes Projekt 10000 Abhängigkeiten hinzu :-).
Marvin
4
var a = { b: { c: 9 } };

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

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

    return layer;
};

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

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

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

Dies ist eine Menge Code im Vergleich zu der viel einfacheren evalMethode, aber wie Simon Willison sagt, sollten Sie niemals eval verwenden .

Auch JSFiddle .

McKayla
quelle
Großartig, das ist ein Genuss und kürzer als CD Sanchez. Vielen Dank.
Nevf
Wert (a, 'bbbbb') gibt nicht undefiniert zurück
frumbert
4

Ich habe die elegante Antwort um ninjagecko erweitert, sodass die Funktion sowohl gepunktete als auch / oder Array-Stilreferenzen verarbeitet und eine leere Zeichenfolge bewirkt, dass das übergeordnete Objekt zurückgegeben wird.

Bitte schön:

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

Siehe mein funktionierendes jsFiddle-Beispiel hier: http://jsfiddle.net/sc0ttyd/q7zyd/

Sc0ttyD
quelle
Wirklich gute Lösung. Es gibt nur ein Problem, es wird davon []ausgegangen , dass die Notation immer für Arrays gilt. Auf diese Weise können auch Objektschlüssel dargestellt werden. obj['some-problem/name'].list[1]Um dies zu beheben, musste ich die arr_derefFunktion wie javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
folgt
1
Obwohl ich das heutzutage nicht tun würde. Ich würde Lodash verwenden: lodash.com/docs#get
Sc0ttyD
2
var find = function(root, path) {
  var segments = path.split('.'),
      cursor = root,
      target;

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

  return cursor;
};

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

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

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

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

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

find(obj, "a.k")(); // hello world
Cristian Sanchez
quelle
Vielen Dank für all die schnellen Antworten. Mag die eval () - Lösungen nicht. Dieser und ähnliche Beiträge sehen am besten aus. Ich habe jedoch immer noch ein Problem. Ich versuche den Wert obj.ab = neuer Wert zu setzen. Um genau zu sein, ist der Wert von b eine Funktion, daher muss ich obj.ab (new_value) verwenden. Die Funktion wird aufgerufen, aber der Wert wird nicht festgelegt. Ich denke, es ist ein Umfangsproblem, aber ich grabe immer noch. Mir ist klar, dass dies außerhalb des Rahmens der ursprünglichen Frage liegt. Mein Code verwendet Knockout.js und b ist ein ko.observable.
Nevf
@nevf: Ich habe eine zweite Funktion hinzugefügt, die meiner Meinung nach das macht, was Sie wollen. Sie können es je nach gewünschtem Verhalten nach Ihren Wünschen anpassen (z. B. sollten die Objekte erstellt werden, wenn sie nicht vorhanden sind? Usw.).
Cristian Sanchez
@nevf Aber meins macht es mit einer Funktion. ; D
McKayla
Danke für das Update, das ich nutzen konnte. @tylermwashburn - und vielen Dank für Ihre kürzere Implementierung, die auch ein Vergnügen ist. Habt alle ein tolles W / E.
Nevf
2

Sie können den Wert eines Objektelements durch Punktnotation mit einer einzelnen Codezeile erhalten:

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

In Ihrem Fall:

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

Zur Vereinfachung können Sie eine Funktion wie die folgende schreiben:

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

Erläuterung:

Der Funktionskonstruktor erstellt ein neues Funktionsobjekt. In JavaScript ist jede Funktion tatsächlich ein Funktionsobjekt. Die Syntax zum expliziten Erstellen einer Funktion mit dem Funktionskonstruktor lautet:

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

Dabei arguments(arg1 to argN)muss es sich um eine Zeichenfolge handeln, die einem gültigen JavaScript-Bezeichner und entsprichtfunctionBody eine Zeichenfolge enthält, die die JavaScript-Anweisungen enthält, aus denen die Funktionsdefinition besteht.

In unserem Fall nutzen wir den Vorteil des String-Funktionskörpers, um ein Objektelement mit Punktnotation abzurufen.

Ich hoffe es hilft.

Harish Anchu
quelle
Können Sie genau erklären, was dies tut? Und wie würden Sie 'ab' usw. als Funktionsparameter übergeben?
Nevf
1
Ich habe dies mit +1 bewertet, aber JSLint warnt davor, dass "der Funktionskonstruktor eine Form der Auswertung ist" .
gabe
2

GET / SET- Antwort, die auch in React Native funktioniert (die Sie derzeit nicht zuweisen können Object.prototype):

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

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

Verwendung:

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

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

Ich habe das Folgende aus Ricardo Tomasis Antwort kopiert und modifiziert, um auch Unterobjekte zu erstellen, die bei Bedarf noch nicht existieren. Es ist etwas weniger effizient (mehrif s und das Erstellen leerer Objekte), sollte aber ziemlich gut sein.

Außerdem können wir dort tun, Object.prop(obj, 'a.b', false)wo wir vorher nicht konnten. Leider lässt es uns immer noch nicht zuweisen undefined... Ich bin mir noch nicht sicher, wie ich das anstellen soll.

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

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

Wenn Sie ein Objekt, das Punktnotationsschlüssel enthält, in eine Array-Version dieser Schlüssel konvertieren möchten, können Sie dies verwenden.


Dies wird so etwas wie konvertieren

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

zu

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

convertDotNotationToArray(objectWithDotNotation) {

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

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

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

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

}
Matthew Mullin
quelle
Die Werte wurden nicht mit "Brothers.0.one" wie JSON verarbeitet.
Karthick
Haben Sie Vorschläge für die Verarbeitung komplexerer JSON-Dateien mit Array-Sammlung?
Karthick
0

Hier ist mein Code ohne Verwendung eval. Es ist auch leicht zu verstehen.

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

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

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

Ja, es wurde vor 4 Jahren gefragt, und ja, das Erweitern von Basisprototypen ist normalerweise keine gute Idee, aber wenn Sie alle Erweiterungen an einem Ort aufbewahren, können sie nützlich sein.
Also, hier ist mein Weg, dies zu tun.

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

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

        return result;
    },
    enumerable: false
});

Jetzt können Sie überall verschachtelte Eigenschaften abrufen, ohne ein Modul mit Funktion oder Kopier- / Einfügefunktion zu importieren.

UPD.Beispiel:

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

UPD 2. Die nächste Erweiterung bricht Mungo in meinem Projekt. Ich habe auch gelesen, dass es jquery kaputt machen könnte. Also mach es niemals auf die nächste Weise

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

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

    return result;
};
Michael Kapustey
quelle
0

Hier ist meine Implementierung

Implementierung 1

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

Implementierung 2 (mit Array Reduce anstelle von Slice)

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

Beispiele:

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

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

Es kann auch Objekte in Arrays wie für behandeln

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

Dies ist meine erweiterte Lösung, vorgeschlagen von: ninjagecko

Für mich war eine einfache String-Notation nicht genug, daher unterstützt die folgende Version Dinge wie:

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

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

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

    var _arrIndex = null;

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

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

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

Auf die Gefahr hin, ein totes Pferd zu schlagen ... Ich finde dies am nützlichsten, wenn ich verschachtelte Objekte durchquere, um zu verweisen, wo Sie sich in Bezug auf das Basisobjekt oder ein ähnliches Objekt mit derselben Struktur befinden. Zu diesem Zweck ist dies bei einer Durchlauffunktion für verschachtelte Objekte nützlich. Beachten Sie, dass ich ein Array verwendet habe, um den Pfad zu halten. Es wäre trivial, dies so zu ändern, dass entweder ein Zeichenfolgenpfad oder ein Array verwendet wird. Beachten Sie auch, dass Sie dem Wert im Gegensatz zu einigen anderen Implementierungen "undefiniert" zuweisen können.

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

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

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

Srkleiman
quelle
0

Ich habe diesen Code in meinem Projekt verwendet

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

Verwendung:

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

Einige Jahre später fand ich dies, das Umfang und Array behandelt. z.Ba['b']["c"].d.etc

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

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

  return obj;
}

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

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

Es ist nicht klar, was Ihre Frage ist. Wenn Sie Ihr Objekt erhalten, erhalten obj.a.bSie "2", so wie es ist. Wenn Sie die Zeichenfolge so manipulieren möchten, dass Klammern verwendet werden, können Sie Folgendes tun:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Mark Eirich
quelle
1
Dies funktioniert nicht a.b.cund erreicht nicht wirklich das, was sie wollen. Sie wollen den Wert, keinen evalPfad.
McKayla
Ich habe es jetzt behoben, damit es funktioniert a.b.c, aber Sie haben Recht, anscheinend wollte er den Wert der Eigenschaft auf / setzen obj.a.b. Die Frage war für mich verwirrend, da er sagte, er wolle "die Saite konvertieren" ...
Mark Eirich
Gut gemacht. :) Es war ein wenig vage. Sie haben jedoch gute Arbeit geleistet.
McKayla
-1

Hier sind meine 10 Cent für den Fall, die folgende Funktion wird basierend auf dem angegebenen Pfad abgerufen / eingestellt. Sicher, dass Sie sie verbessern können, entfernen || und ersetzen Sie es durch, Object.hasOwnPropertywenn Sie sich versehentlich um falsche Werte kümmern.

Ich habe es mit a.b.cund ab2.c getestet{a:{b:[0,1,{c:7}]}} funktioniert sowohl zum Setzen als auch zum Erhalten :).

cheerz

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