Nokta gösterimindeki JavaScript dizesini nesne başvurusuna dönüştürme


214

Bir JS nesnesi verildi

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

ve bir dize

"a.b"

dizeyi nokta gösterimine nasıl dönüştürebilirim?

var val = obj.a.b

Dize sadece olsaydı, 'a'kullanabilirdim obj[a]. Ancak bu daha karmaşıktır. Bazı basit bir yöntem olduğunu hayal ediyorum ama şu anda kaçıyor.


25
@Andrey evalkötüdür; kullanmayın
kevinji

5
FYI: İşte yaptığım bazı ilginç hız testleri: jsperf.com/dereference-object-property-path-from-string
James Wilkins

perf ciddi bir konuysa ve aynı yolları çok fazla tekrar kullanıyorsanız (örneğin, bir dizi filtresi işlevinin içinde), aşağıdaki cevabımda açıklandığı gibi İşlev yapıcısını kullanın. Aynı yol binlerce kez kullanıldığında, İşlev yöntemi her bir dereference üzerindeki yolu değerlendirmek veya bölmek ve azaltmak için 10 kat daha hızlı olabilir.
Kevin Crumley


Yanıtlar:


437

Son not: Bu cevabın pek çok oy aldığından memnun olduğum halde, biraz dehşete kapıldım. Eğer birinin "xabc" gibi nokta gösterim dizelerini referanslara dönüştürmesi gerekiyorsa, (belki de) çok yanlış bir şeylerin olduğuna dair bir işaret olabilir (belki garip bir serileştirme yapmıyorsanız).

Yani, bu cevaba giden yolu bulan acemiler kendilerine "bunu neden yapıyorum?"

Kullanım durumunuz küçükse ve performans sorunlarıyla karşılaşmayacaksanız, bunu yapmak genellikle iyidir ve daha sonra daha karmaşık hale getirmek için soyutlamanıza dayanmanız gerekmez. Aslında, bu kod karmaşıklığını azaltacak ve işleri basit tutacaksa, muhtemelen devam etmeli ve OP'nin istediği şeyi yapmalısınız. Ancak, durum böyle değilse, bunlardan herhangi birinin geçerli olup olmadığını düşünün:

durum 1 : Verilerinizle çalışmanın birincil yöntemi olarak (örneğin, uygulamanızın nesnelerin etrafından aktarılması ve bunların kaydının silinmesi gibi). "Bir dizeden bir işlevi veya değişken adını nasıl arayabilirim" diye sormak gibi.

  • Bu kötü bir programlama uygulamasıdır (özellikle gereksiz metaprogramlama ve işlev yan etkisi olmayan kodlama stilini ihlal eder ve performans isabetlerine sahip olacaktır). Bu durumda kendilerini bulan acemiler bunun yerine dizi gösterimleriyle, örneğin ['x', 'a', 'b', 'c'] veya mümkünse daha doğrudan / basit / anlaşılır bir şeyle çalışmayı düşünmelidir: kaybetmemek gibi referansların kendileri ilk etapta izler (sadece istemci tarafı veya sadece sunucu tarafı ise en idealdir), vb. (Önceden var olan benzersiz bir kimlik eklemek yetersiz kalır, ancak spesifikasyon gerektiriyorsa kullanılabilir ne olursa olsun var.)

durum 2 : Serileştirilmiş verilerle veya kullanıcıya görüntülenecek verilerle çalışma. Bir Date nesnesi yerine bir tarihi "1999-12-30" dizesi olarak kullanmak gibi (dikkatli değilse saat dilimi hatalarına veya ek serileştirme karmaşıklığına neden olabilir). Ya da ne yaptığını biliyorsun.

  • Bu belki iyi. Nokta dizesi olmadığına dikkat edin. sterilize edilmiş giriş parçalarınızda.

Bu yanıtı her zaman kullanırken ve dize ile dizi arasında geçiş yaparken bulursanız, kötü durumda olabilirsiniz ve bir alternatif düşünmelisiniz.

İşte diğer çözümlerden 10 kat daha kısa olan zarif bir astar:

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

[değiştir] Veya ECMAScript 6'da:

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

(Ben başkalarının önerdiği gibi eval her zaman kötü olduğunu düşünüyorum değil (genellikle olsa da), yine de bu insanlar bu yöntemin eval kullanmadığı için memnun olacak. Yukarıdaki obj.a.b.etcverilen objve dize bulacaksınız "a.b.etc".)

reduceECMA-262 standardında (5. baskı) olmasına rağmen hala kullanmaktan korkanlara yanıt olarak , işte iki satırlı yinelemeli bir uygulama:

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

JS derleyicisinin yaptığı optimizasyonlara bağlı olarak, iç içe işlevlerin her çağrıda olağan yöntemler (bir kapatma, nesne veya genel ad alanına yerleştirme) yoluyla yeniden tanımlanmadığından emin olmak isteyebilirsiniz.

düzenleme :

Yorumlardaki ilginç bir soruyu cevaplamak için:

bunu nasıl bir pasör haline getirirdin? Değerleri yalnızca yoldan döndürmekle kalmaz, aynı zamanda işleve yeni bir değer gönderilirse bunları ayarlar mı? - Swader 28 Haziran, 21:42

(sidenote: ne yazık ki bir Setter ile bir nesneyi geri döndüremez, çünkü bu çağırma kuralını ihlal eder; yorumcu bunun yerine index(obj,"a.b.etc", value)yapmak gibi yan etkileri olan genel bir setter tarzı fonksiyona atıfta bulunuyor gibi görünüyor obj.a.b.etc = value.)

reduceTarzı buna çok da uygun değil ama özyinelemeli uygulama değiştirebilirsiniz:

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

... kişisel olarak ayrı bir fonksiyon yapmayı tavsiye ederim setIndex(...). Sorunun orijinal pozerinin .splitdizelerden ziyade indeks dizileri ile çalışabileceğini (olmalı mı?) Bir yan notla bitirmek istiyorum ; ancak genellikle bir kullanışlılık işlevinde yanlış bir şey yoktur.


Bir yorumcu sordu:

diziler ne olacak? "ab [4] .cd [1] [2] [3]" gibi bir şey mi? -AlexS

Javascript çok garip bir dildir; Genelde nesnelerin özellik anahtarları olarak yalnızca dizeleri olabilir, bu nedenle örneğin xgenel bir nesne olsaydı x={}, ... doğru okudunuz ... evet ...x[1]x["1"]

Javascript Dizileri (kendileri Nesne örneğidir), benzer bir şey yapabilseniz bile, tamsayı anahtarlarını özellikle teşvik eder x=[]; x["puppy"]=5;.

Ancak genel olarak (ve istisnalar vardır), x["somestring"]===x.somestring(izin verildiğinde; yapamazsınız x.123).

(Kullandığınız JS derleyicisinin, spesifikasyonu ihlal etmeyeceğini kanıtlayabiliyorsa, bunları daha akılcı temsillere derlemeyi seçebileceğini unutmayın.)

Bu nedenle, sorunuzun yanıtı, bu nesnelerin yalnızca tamsayıları kabul edip etmediğinize (sorun etki alanınızdaki bir kısıtlama nedeniyle) bağlı olup olmadığınıza bağlıdır. Varsayalım. Bu durumda geçerli bir ifade, temel tanımlayıcının artı bazı .identifiers artı bazı ["stringindex"]s

a["b"][4]["c"]["d"][1][2][3]Muhtemelen de desteklememiz gerekse de, bu buna eşdeğer olacaktır a.b["c\"validjsstringliteral"][3]. Geçerli bir dize değişmezinin nasıl ayrıştırılacağını görmek için dize değişmezleri üzerindeki ecmascript dilbilgisi bölümünü denetlemeniz gerekir . Teknik aolarak, geçerli bir javascript tanımlayıcısı olan (ilk cevabımın aksine) de kontrol etmek istersiniz .

Sorunuza basit cevabı olsa da dizeleri virgül veya parantez içeren yoksa , sadece değil kümesindeki karakterlerin uzunluğu 1+ dizilerini maç için olmak olacak ,ya [ya ]:

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

Dizeleriniz kaçış karakterleri veya "karakterler içermiyorsa ve IdentifierNames, StringLiterals'in bir alt dili olduğundan (bence ???) önce noktalarınızı [] biçimine dönüştürebilirsiniz:

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

Tabii ki, daima dikkatli olun ve asla verilerinize güvenmeyin. Bazı kullanım durumlarında işe yarayabilecek bazı kötü yollar da şunları içerir:

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

Özel 2018 düzenlemesi:

Tam daire gidelim ve sözdizimsel saflık hamfistery yararına ... ile gelebilir en verimsiz, korkunç overmetaprogrammed çözüm yapalım. ES6 Proxy nesneleri ile! ... (imho iyi ve harika ama) yanlış yazılmış kütüphaneleri bozabilecek bazı özellikleri de tanımlayalım. Performansı, akıl sağlığını (sizin veya başkalarının), işinizi vb. Önemsiyorsanız, bunu kullanmaya dikkat etmelisiniz.

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

Çıktı:

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

(proxy geçersiz kılma get) objHyper ['abc']: 1

(proxy geçersiz kılma kümesi) objHyper ['abc'] = 3, şimdi obj: {"a": {"b": {"c": 3, "d": 2}}}

(sahne arkasında) objHyper: Proxy {a: {…}}

(kısayol) obj.H ['abc'] = 4

[kısayol) obj.H ['abc'] obj ['a'] ['b'] ['c']: 4

verimsiz fikir: Girdi bağımsız değişkenini temel alarak gönderilecekleri değiştirebilirsiniz; ya .match(/[^\]\[.]+/g)yöntemi desteklemek için kullanın obj['keys'].like[3]['this']veya eğer instanceof Arraybir Array'ı girdi gibi kabul edin keys = ['a','b','c']; obj.H[keys].


Belki de tanımsız indeksleri 'daha yumuşak' bir NaN-tarzı bir şekilde ele almak istediğiniz öneri başına (örneğin index({a:{b:{c:...}}}, 'a.x.c')yakalanmamış TypeError yerine tanımsız olarak geri dön) ...:

1) Bu, 1 boyutlu dizin durumunda "bir hata atmak yerine tanımsız dönmeliyiz" perspektifinden anlamlıdır ({}) ['örneğin'] == tanımsız, bu nedenle " hatası "hatası.

2) Bu , yukarıdaki örnekte bir TypeError hatasıyla başarısız olacağımız, yaptığımız perspektiften anlamlı değildirx['a']['x']['c'] .

Bununla birlikte, azaltma işlevinizi aşağıdakilerden biriyle değiştirerek bu işi yapacaksınız:

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

(Bir for döngüsü kullanarak ve bir sonraki dizine eklediğiniz alt sonuç tanımsız olduğunda kırma / döndürme veya bu tür hataların yeterince nadir olmasını bekliyorsanız bir try-catch kullanarak bunu daha verimli hale getirebilirsiniz.)


4
reduceşu anda kullanılan tüm tarayıcılarda desteklenmemektedir.
Ricardo Tomasi

14
@Ricardo: Array.reduceECMA-262 standardının bir parçasıdır. Eski tarayıcıları gerçekten desteklemek istiyorsanız, bir Array.prototype.reduceyerde verilen örnek uygulamayı tanımlayabilirsiniz (örn. Developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko

2
Evet, ancak iki satırı bir işleve koymak yeterince kolaydır. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf

3
Bu zarif örneği seviyorum, teşekkürler ninjagecko. Dizi stilini ve boş dizeleri işlemek için genişlettim
buradaki

2
@ninjagecko bunu nasıl ayarlayıcıya dönüştürürdün? Değerleri yalnızca yoldan döndürmekle kalmaz, aynı zamanda işleve yeni bir değer gönderilirse de ayarlanır?
Kundakçı

64

Lodash'ı kullanabiliyorsanız , tam olarak bunu yapan bir işlev vardır:

_.get (nesne, yol, [defaultValue])

var val = _.get(obj, "a.b");

4
Not: _.get(object, path)bir yol bulunamazsa kesilmez. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)yapar. Özel durumum için - her durum için değil - tam olarak ihtiyacım olan şey. Teşekkürler!
Bay B.

1
Mr.B @. Lodash'ın en son sürümü için üçüncü, isteğe bağlı bir argüman vardır defaultValue. _.get()Metot bize eğer varsayılan değer _.get()giderir için undefinedistediğiniz ve belirlediğiniz değeri izle ne olursa olsun, bu yüzden ayarlayın.
steampowered

7
Merak eden herkes için de destekliyor _.set(object, path, value).
Jeffrey Roosendaal

19

Özyineleme ile biraz daha ilgili bir örnek.

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

19

Ayrıca lodash.get kullanabilirsiniz

Bu paketi (npm i --save lodash.get) yükledikten sonra şu şekilde kullanın:

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;

10

Aynı yolu birçok kez referanstan arındırmayı bekliyorsanız, her nokta gösterim yolu için bir işlev oluşturmak aslında en iyi performansa sahiptir (James Wilkins'in yukarıdaki yorumlarda bağlandığı mükemmel testlere genişleme).

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

İşlev yapıcısının kullanılması, eval () ile güvenlik ve en kötü durum performansı açısından bazı dezavantajlara sahiptir, ancak IMO, aşırı dinamizm ve yüksek performansın bir kombinasyonuna ihtiyaç duyduğunuz durumlar için az kullanılan bir araçtır. Dizi filtre işlevleri oluşturmak ve bir AngularJS özet döngü içinde çağırmak için bu yöntemi kullanın. Benim profiller dinamik olarak tanımlanmış yolları 3-4 seviyeleri derin kullanarak, dereference ve yaklaşık 2000 karmaşık nesneleri filtre için 1 ms'den daha az alan array.filter () adım sürekli gösterir.

Benzer bir metodoloji, ayarlayıcı işlevler oluşturmak için elbette kullanılabilir:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");

1
aynı yolları uzun süre ayırmanız gerektiğinde, yukarıdaki jsperf.com bağlantısı daha sonra işlevi nasıl kaydedeceğinize ve arayacağınıza ilişkin bir örnek gösterir. İşlev yapıcısını çağırma eylemi oldukça yavaştır, bu nedenle yüksek perf kodunun mümkünse tekrarlanmasını önlemek için sonuçları hatırlaması gerekir.
Kevin Crumley


7

Yolu ayırmanızı ve yinelemenizi ve sahip olduğunuz nesneyi azaltmanızı öneririm. Bu teklif , eksik özellikler için varsayılan bir değerle çalışır .

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


6

Zaten Lodash kullanıyorsanız propertyveya getişlevlerini kullanabileceğinizi unutmayın :

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

Alt çizgi de bir propertyişleve sahiptir ancak nokta gösterimini desteklemez.


1
Görünüşe göre Undercore nokta gösterimini desteklemiyor. Yanıt güncellendi.
16'da Tamlyn

5

Diğer teklifler biraz şifreli, bu yüzden katkıda bulunacağımı düşündüm:

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

5

Bu işlemi basitleştiren npm'de bulunan kütüphaneyi kullanabilirsiniz. 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!

1
Bunu dikkatimize sunduğunuz için teşekkür ederiz. Çok faydalı görünüyor.
nevf

İşte bu yüzden her proje 10000 bağımlılık enjekte ediyor :-).
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

Bu, evalbunu yapmanın çok daha basit yoluna kıyasla çok fazla kod , ancak Simon Willison'un dediği gibi, asla eval kullanmamalısınız .

Ayrıca, JSFiddle .


Harika, bu bir tedavi işe yarıyor ve CD Sanchez'den daha kısa. Teşekkürler.
nevf

değeri (a, 'bbbbb') undefined döndürmez
frumbert

4

Fonksiyonu noktalı ve / veya dizi stili başvuruları işleyecek ve boş bir dize üst nesnenin döndürülmesine neden olacak şekilde zarif cevap ninjagecko tarafından genişletilmiş.

Hadi bakalım:

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

Benim çalışma jsFiddle örneğini burada görebilirsiniz: http://jsfiddle.net/sc0ttyd/q7zyd/


Gerçekten iyi bir çözüm. Tek bir sorun var, []gösterimin her zaman diziler için olduğunu varsayar . Bu şekilde temsil edilen nesne anahtarları da olabilir, örneğin obj['some-problem/name'].list[1]Bunu düzeltmek için arr_derefböyle bir işlevi güncellemem javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
gerekti

1
Her ne kadar bugünlerde bunu yapmam. Lodash kullanırdım: 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

Tüm hızlı yanıtlar için teşekkürler. Eval () çözümlerinden hoşlanmayın. Bu ve benzer gönderiler en iyi görünüyor. Ancak hala bir sorunum var. Ben obj.ab = yeni değer değerini ayarlamaya çalışıyorum. Kesin olmak için b değeri bir işlev bu yüzden obj.ab (new_value) kullanmanız gerekir. İşlev çağrılır, ancak değer ayarlanmaz. Bence bu bir kapsam sorunu ama hala kazıyorum. Bunun orijinal sorunun kapsamı dışında olduğunun farkındayım. Kodum Knockout.js kullanıyor ve b bir ko.observable.
nevf

@ nevf: İstediğini yaptığını düşündüğüm ikinci bir işlev ekledim. İstediğiniz davranışa bağlı olarak isteğinize göre özelleştirebilirsiniz (örneğin, yoksa, nesneleri oluşturmalı mı? Vb.).
Cristian Sanchez

@nevf Ama benimki tek bir işlevle yapıyor. ; D
McKayla

kullanabildiğim güncelleme için teşekkürler. @tylermwashburn - ve aynı zamanda bir tedavi de sağlayan daha kısa uygulamanız için teşekkürler. Harika bir w / e hepsi var.
nevf

2

Tek bir kod satırı ile nokta gösterimi ile bir nesne üyesinin değerini elde edebilirsiniz:

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

Sizin durumunuzda:

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

Basitleştirmek için şöyle bir işlev yazabilirsiniz:

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

Açıklama:

İşlev yapıcısı yeni bir İşlev nesnesi oluşturur. JavaScript'te her işlev aslında bir İşlev nesnesidir. İşlev yapıcısı ile açıkça bir işlev oluşturmak için sözdizimi şöyledir:

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

burada arguments(arg1 to argN)geçerli bir javaScript tanımlayıcısına karşılık gelen functionBodyve işlev tanımını içeren javaScript deyimlerini içeren bir dize olmalıdır.

Bizim durumumuzda nokta elemanıyla nesne üyesini almak için string fonksiyon gövdesinden yararlanıyoruz.

Umarım yardımcı olur.


Bunun ne yaptığını tam olarak açıklayabilir misiniz? Ve 'ab' vb. Bir fonksiyon parametresi olarak nasıl iletilir?
nevf

1
Buna +1 verdim ama JSLint "İşlev yapıcısının bir eval türü olduğunu " söylüyor .
gabe

2

Aynı zamanda yerel olarak da yanıt veren GET / SET cevabı ( Object.prototypeşu anda atayamazsınız ):

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

Kullanımı:

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

1

Ricardo Tomasi'nin cevabından aşağıdakileri kopyaladım ve henüz gerekli olmayan alt nesneleri oluşturmak için değiştirdim. Biraz daha az verimli (daha fazla ifve boş nesneler oluşturma), ancak oldukça iyi olmalı.

Ayrıca, daha Object.prop(obj, 'a.b', false)önce yapamadığımız yerleri yapmamıza izin verecektir . Ne yazık ki, hala atamamıza izin vermiyor undefined... Henüz bunun hakkında nasıl emin olacağım .

/**
 * 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]
 }

1

Nokta gösterim anahtarları içeren herhangi bir nesneyi bu anahtarların sıralı bir sürümüne dönüştürmek istiyorsanız bunu kullanabilirsiniz.


Bu gibi bir şeyi dönüştürecek

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

için

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

}

JSON gibi değerleri "brothers.0.one" ile işlemedi.
Karthick

Dizi koleksiyonu ile daha karmaşık JSON işlemek için herhangi bir öneriniz var mı?
Karthick

0

İşte benim kod kullanmadan eval. Bunu anlamak da kolay.

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

0

Evet, 4 yıl önce soruldu ve evet, temel prototipleri genişletmek genellikle iyi bir fikir değil, ancak tüm uzantıları tek bir yerde tutarsanız yararlı olabilir.
İşte bunu yapmanın yolu.

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

Artık işlevli veya kopyalama / yapıştırma işlevli modülü içe aktarmadan her yerde yuvalanmış özellik elde edebileceksiniz.

UPD.Example:

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

UPD 2. Bir sonraki uzantı projemde mongoose kırdı. Ayrıca jquery kırabileceğini okudum. Yani, asla bir sonraki şekilde yapma

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

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

    return result;
};

0

İşte benim uygulama

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

Uygulama 2 (dilim yerine dizi azaltma özelliğini kullanarak)

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

Örnekler:

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

diziler içindeki nesneleri de

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

0

Bu benim önerdiğim genişletilmiş çözüm: ninjagecko

Benim için basit dize gösterimi yeterli değildi, bu yüzden aşağıdaki sürüm aşağıdaki gibi şeyleri destekliyor:

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

0

Ölü bir atı yenme riski altında ... Bunu en çok, temel nesneye veya aynı yapıya sahip benzer bir nesneye göre bulunduğunuz yere başvurmak için iç içe nesneler arasında gezinirken faydalı buluyorum. Bu amaçla, bu, iç içe geçmiş bir nesne geçiş işlevi ile kullanışlıdır. Yolu tutmak için bir dizi kullandığımı unutmayın. Bunu bir dize yolu veya bir dizi kullanacak şekilde değiştirmek önemsiz olacaktır. Ayrıca, diğer uygulamalardan farklı olarak, değere "tanımsız" atayabileceğinizi unutmayın.

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


0

Bu kodu projemde kullandım

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

Kullanımı:

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

0

Birkaç yıl sonra, kapsamı ve diziyi işleyen bunu buldum. Örneğina['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

-1

Sorunuzun ne olduğu belli değil. Nesneniz göz önüne alındığında, obj.a.bsize olduğu gibi "2" verir. Köşeli parantez kullanmak için dizeyi değiştirmek istiyorsanız, bunu yapabilirsiniz:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]

1
Bu işe yaramıyor a.b.cve istediklerini gerçekten gerçekleştiremiyor .. Değeri istiyorlar, bir evalyol değil .
McKayla

Şimdi bu yüzden çalışır ile sabit a.b.c, ama haklısın, görünüşe göre o / mülkiyet değerini ayarlamak istedim obj.a.b. Bu soru benim için kafa karıştırıcıydı, çünkü “ipi dönüştürmek” istediğini söyledi ....
Mark Eirich

Aferin. :) Biraz belirsizdi. Yine de iyi bir dönüşüm işi yaptın.
McKayla

-1

İşte benim durumda 10 sent, feryat işlevi sağlanan yola göre alır / ayarlanır, .. u u geliştirebilir, kaldırmak || ve Object.hasOwnPropertyyanlışlıkla yanlış değerlere önem veriyorsanız,

i a.b.cve ab2.c {a:{b:[0,1,{c:7}]}}ve çalışmalarını hem ayar hem de almak için test etti :).

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)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.