Konversi string JavaScript dalam notasi titik menjadi referensi objek

214

Diberikan objek JS

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

dan sebuah string

"a.b"

bagaimana saya bisa mengubah string ke notasi titik sehingga saya bisa pergi

var val = obj.a.b

Jika string itu adil 'a', saya bisa menggunakan obj[a]. Tetapi ini lebih kompleks. Saya membayangkan ada beberapa metode sederhana tetapi lolos saat ini.

nevf
sumber
25
@Andrey evalitu jahat; jangan gunakan itu
kevinji
5
FYI: Berikut adalah beberapa tes kecepatan menarik yang baru saja saya lakukan: jsperf.com/dereference-object-property-path-from-string
James Wilkins
jika perf merupakan pertimbangan serius dan Anda sering menggunakan kembali jalur yang sama (misalnya di dalam fungsi filter array), gunakan Function constructor seperti dijelaskan dalam jawaban saya di bawah ini. Ketika lintasan yang sama digunakan ribuan kali, metode Fungsi dapat lebih dari 10x secepat evaling atau membelah dan mengurangi lintasan di setiap dereferensi.
Kevin Crumley

Jawaban:

437

catatan baru-baru ini: Walaupun saya tersanjung bahwa jawaban ini telah mendapatkan banyak upvotes, saya juga agak ngeri. Jika seseorang perlu mengubah string notasi titik seperti "xabc" menjadi referensi, itu bisa (mungkin) menjadi tanda bahwa ada sesuatu yang sangat salah terjadi (kecuali mungkin Anda melakukan deserialisasi aneh).

Dengan kata lain, para pemula yang menemukan jalan mereka untuk jawaban ini harus bertanya pada diri sendiri pertanyaan "mengapa saya melakukan ini?"

Tentu saja umumnya baik-baik saja untuk melakukan ini jika use case Anda kecil dan Anda tidak akan mengalami masalah kinerja, DAN Anda tidak perlu membangun berdasarkan abstraksi Anda untuk membuatnya lebih rumit nanti. Bahkan, jika ini akan mengurangi kompleksitas kode dan menjaga hal-hal sederhana, Anda mungkin harus melanjutkan dan melakukan apa yang diminta OP. Namun, jika bukan itu masalahnya, pertimbangkan apakah ini berlaku:

kasus 1 : Sebagai metode utama untuk bekerja dengan data Anda (misalnya sebagai bentuk default aplikasi Anda untuk melewatkan objek di sekitar dan mendereferensi mereka). Seperti bertanya "bagaimana saya bisa mencari fungsi atau nama variabel dari sebuah string".

  • Ini adalah praktik pemrograman yang buruk (metaprogramming yang tidak perlu secara khusus, dan semacam melanggar gaya pengkodean fungsi-efek-bebas, dan akan memiliki hit kinerja). Murid yang menemukan diri mereka dalam kasus ini, sebaliknya harus mempertimbangkan bekerja dengan representasi array, misalnya ['x', 'a', 'b', 'c'], atau bahkan sesuatu yang lebih langsung / sederhana / langsung jika mungkin: seperti tidak kehilangan melacak referensi sendiri di tempat pertama (paling ideal jika hanya sisi-klien atau hanya sisi-server), dll. (ID unik yang sudah ada sebelumnya akan tidak dapat ditambahkan, tetapi dapat digunakan jika spesifikasi sebaliknya mensyaratkan keberadaan terlepas.)

kasus 2 : Bekerja dengan data serial, atau data yang akan ditampilkan kepada pengguna. Seperti menggunakan tanggal sebagai string "1999-12-30" daripada objek Tanggal (yang dapat menyebabkan bug zona waktu atau menambah kompleksitas serialisasi jika tidak hati-hati). Atau Anda tahu apa yang Anda lakukan.

  • Ini mungkin baik. Berhati - hatilah karena tidak ada string dot "." dalam fragmen input yang telah disanitasi.

Jika Anda menemukan diri Anda menggunakan jawaban ini sepanjang waktu dan mengubah bolak-balik antara string dan array, Anda mungkin berada dalam kasus yang buruk, dan harus mempertimbangkan alternatif.

Berikut ini adalah one-liner elegan yang 10x lebih pendek dari solusi lain:

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

[Sunting] Atau dalam ECMAScript 6:

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

(Bukannya saya pikir eval selalu buruk seperti yang disarankan orang lain (meskipun biasanya demikian), namun orang-orang akan senang bahwa metode ini tidak menggunakan eval. Di atas akan ditemukan obj.a.b.etcdiberikan objdan string "a.b.etc".)

Menanggapi mereka yang masih takut menggunakan reducemeskipun berada dalam standar ECMA-262 (edisi ke-5), berikut adalah implementasi rekursif dua-baris:

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

Bergantung pada optimisasi yang dilakukan oleh kompiler JS, Anda mungkin ingin memastikan bahwa fungsi yang disarangkan tidak didefinisikan ulang pada setiap panggilan melalui metode yang biasa (menempatkannya dalam penutupan, objek, atau namespace global).

edit :

Untuk menjawab pertanyaan yang menarik di komentar:

bagaimana Anda mengubahnya menjadi setter juga? Tidak hanya mengembalikan nilai dengan jalur, tetapi juga mengaturnya jika nilai baru dikirim ke fungsi? - Swader 28 Juni pukul 21:42

(sidenote: sayangnya tidak bisa mengembalikan objek dengan Setter, karena itu akan melanggar konvensi pemanggilan; komentator tampaknya merujuk pada fungsi gaya setter umum dengan efek samping seperti index(obj,"a.b.etc", value)melakukan obj.a.b.etc = value.)

The reduceGaya ini tidak benar-benar cocok untuk itu, tapi kita bisa memodifikasi pelaksanaan rekursif:

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

... meskipun secara pribadi saya akan merekomendasikan membuat fungsi terpisah setIndex(...). Saya ingin mengakhiri dengan catatan tambahan bahwa pelamar asli dari pertanyaan bisa (harus?) Bekerja dengan array indeks (yang dapat mereka peroleh .split), daripada string; meskipun biasanya tidak ada yang salah dengan fungsi kenyamanan.


Seorang komentator bertanya:

bagaimana dengan array? sesuatu seperti "ab [4] .cd [1] [2] [3]"? –AlexS

Javascript adalah bahasa yang sangat aneh; pada umumnya objek hanya dapat memiliki string sebagai kunci properti mereka, jadi misalnya jika xitu adalah objek generik seperti x={}, maka x[1]akan menjadi x["1"]... Anda membacanya dengan benar ... ya ...

Array Javascript (yang merupakan turunan dari Object) secara khusus mendorong kunci integer, meskipun Anda dapat melakukan sesuatu seperti x=[]; x["puppy"]=5;.

Tetapi secara umum (dan ada pengecualian), x["somestring"]===x.somestring(ketika diizinkan; Anda tidak bisa melakukannya x.123).

(Perlu diingat bahwa kompiler JS apa pun yang Anda gunakan dapat memilih, mungkin, untuk mengkompilasi ini ke representasi yang lebih waras jika dapat membuktikannya tidak akan melanggar spesifikasi.)

Jadi jawaban untuk pertanyaan Anda akan tergantung pada apakah Anda mengasumsikan objek tersebut hanya menerima bilangan bulat (karena batasan dalam domain masalah Anda), atau tidak. Mari kita anggap tidak. Kemudian ekspresi yang valid adalah gabungan dari pengidentifikasi basis ditambah beberapa .identifiers ditambah beberapa ["stringindex"]s

Ini kemudian akan setara dengan a["b"][4]["c"]["d"][1][2][3], meskipun kita mungkin juga harus mendukung a.b["c\"validjsstringliteral"][3]. Anda harus memeriksa bagian tata bahasa ecmascript pada string literal untuk melihat cara mengurai string literal yang valid. Secara teknis Anda juga ingin memeriksa (tidak seperti dalam jawaban pertama saya) yang amerupakan pengidentifikasi javascript yang valid .

Namun, jawaban sederhana untuk pertanyaan Anda, jika string Anda tidak mengandung koma atau tanda kurung , akan sama dengan mencocokkan panjang 1+ urutan karakter yang tidak ada di set ,atau [atau ]:

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

Jika string Anda tidak mengandung karakter melarikan diri atau "karakter , dan karena IdentifierNames adalah subbahasa dari StringLiterals (saya pikir ???), Anda dapat terlebih dahulu mengonversi titik Anda ke []:

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

Tentu saja, selalu berhati-hati dan jangan pernah mempercayai data Anda. Beberapa cara buruk untuk melakukan ini yang mungkin berhasil untuk beberapa kasus penggunaan juga termasuk:

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

Sunting khusus 2018:

Mari kita mulai dan melakukan solusi terprogram yang paling tidak efisien, mengerikan, dan terlalu terprogram yang dapat kami buat ... untuk kepentingan pemurnian purtas sintaksis . Dengan objek Proxy ES6! ... Mari kita juga mendefinisikan beberapa properti yang (imho baik-baik saja tetapi luar biasa) dapat memecah pustaka yang ditulis secara tidak benar. Anda mungkin harus waspada menggunakan ini jika Anda peduli dengan kinerja, kewarasan (milik Anda atau orang lain), pekerjaan Anda, dll.

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

Keluaran:

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

(proxy override get) objHyper ['abc'] adalah: 1

(proxy override set) objHyper ['abc'] = 3, sekarang obj adalah: {"a": {"b": {"c": 3, "d": 2}}}

(di belakang layar) objHyper adalah: Proxy {a: {...}}

(pintas) obj.H ['abc'] = 4

(pintas) obj.H ['abc'] adalah obj ['a'] ['b'] ['c'] adalah: 4

ide yang tidak efisien: Anda dapat memodifikasi hal di atas untuk dikirim berdasarkan argumen input; baik menggunakan .match(/[^\]\[.]+/g)metode untuk mendukung obj['keys'].like[3]['this'], atau jika instanceof Array, maka hanya menerima Array sebagai input keys = ['a','b','c']; obj.H[keys].


Per saran yang mungkin Anda ingin menangani indeks tidak terdefinisi dengan gaya NaN 'lebih lembut' (mis. index({a:{b:{c:...}}}, 'a.x.c')Mengembalikan TypeError yang tidak terdefinisi daripada yang tidak tertangkap) ...::

1) Ini masuk akal dari perspektif "kita harus mengembalikan undefined daripada melemparkan kesalahan" dalam situasi indeks 1-dimensi ({}) ['eg'] == undefined, jadi "kita harus mengembalikan undefined daripada melemparkan kesalahan "dalam situasi dimensi-N.

2) Hal ini tidak masuk akal dari perspektif yang kita lakukan x['a']['x']['c'], yang akan gagal dengan TypeError dalam contoh di atas.

Yang mengatakan, Anda akan membuat pekerjaan ini dengan mengganti fungsi pengurangan Anda dengan:

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

(Anda dapat menjadikan ini lebih efisien dengan menggunakan for for dan break / return setiap kali subres yang akan Anda indeks selanjutnya tidak terdefinisi, atau menggunakan try-catch jika Anda berharap kegagalan seperti itu cukup langka.)

ninjagecko
sumber
4
reducetidak didukung di semua browser yang saat ini digunakan.
Ricardo Tomasi
14
@ Ricardo: Array.reduceadalah bagian dari standar ECMA-262. Jika Anda benar-benar ingin mendukung browser yang sudah ketinggalan zaman, Anda dapat menentukan Array.prototype.reduceimplementasi sampel yang diberikan di suatu tempat (misalnya developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko
2
Ya tapi cukup mudah untuk menempatkan dua baris ke suatu fungsi. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf
3
Saya suka contoh elegan ini, terima kasih ninjagecko. Saya telah memperluasnya untuk menangani notasi gaya array, serta string kosong - lihat contoh saya di sini: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD
2
@ninjagecko bagaimana Anda mengubahnya menjadi setter juga? Tidak hanya mengembalikan nilai dengan jalur, tetapi juga mengaturnya jika nilai baru dikirim ke fungsi?
Swader
64

Jika Anda dapat menggunakan lodash , ada fungsi, yang melakukan hal itu:

_.get (objek, path, [defaultValue])

var val = _.get(obj, "a.b");
Petar Ivanov
sumber
4
Catatan: _.get(object, path)tidak rusak jika jalan tidak ditemukan. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)tidak. Untuk kasus khusus saya - bukan untuk setiap kasus - persis apa yang saya butuhkan. Terima kasih!
Tn. B.
1
@ Mr.B. versi terbaru dari Lodash memiliki argumen ketiga, opsional, untuk defaultValue. The _.get()kembali metode nilai default jika _.get()resolve ke undefined, jadi set ke apa pun yang Anda inginkan dan menonton untuk nilai yang ditetapkan.
steampowered
7
Bagi yang bertanya-tanya, ini juga mendukung _.set(object, path, value).
Jeffrey Roosendaal
19

Contoh yang sedikit lebih terlibat dengan rekursi.

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
sumber
19

Anda juga bisa menggunakan lodash.get

Anda cukup menginstal paket ini (npm i --simpan lodash.get) dan kemudian gunakan seperti ini:

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
sumber
10

Jika Anda berharap untuk mengulang jalur yang sama berkali-kali, membangun fungsi untuk setiap jalur notasi titik sebenarnya memiliki kinerja terbaik sejauh ini (memperluas tes perf yang dikaitkan dengan James Wilkins dalam komentar di atas).

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

Menggunakan Function constructor memiliki beberapa kelemahan yang sama dengan eval () dalam hal keamanan dan kinerja kasus terburuk, tetapi IMO itu adalah alat yang kurang dimanfaatkan untuk kasus-kasus di mana Anda memerlukan kombinasi dinamisme ekstrem dan kinerja tinggi. Saya menggunakan metodologi ini untuk membangun fungsi filter array dan memanggilnya di dalam loop intisari AngularJS. Profil saya secara konsisten menunjukkan langkah array.filter () yang membutuhkan kurang dari 1 ms untuk melakukan dereferensi dan memfilter sekitar 2000 objek kompleks, menggunakan jalur yang ditentukan secara dinamis dengan kedalaman 3-4 tingkat.

Metodologi serupa dapat digunakan untuk membuat fungsi setter, tentu saja:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");
Kevin Crumley
sumber
1
jika Anda perlu melakukan dereferensi jalur yang sama untuk waktu yang lama, tautan jsperf.com di atas menunjukkan contoh cara menyimpan dan mencari fungsi nanti. Tindakan memanggil Konstruktor fungsi cukup lambat, jadi kode berperforma tinggi harus memoize hasil untuk menghindari mengulanginya jika memungkinkan.
Kevin Crumley
7

Saya sarankan untuk membagi path dan iterate dan mengurangi objek yang Anda miliki. Proposal ini berfungsi dengan nilai default untuk properti yang hilang.

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
sumber
6

Catatan jika Anda sudah menggunakan Lodash, Anda dapat menggunakan propertyatau getfungsinya:

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

Garis bawah juga memiliki propertyfungsi tetapi tidak mendukung notasi titik.

Tamlyn
sumber
1
Tampaknya Underscore tidak mendukung notasi titik. Jawaban diperbarui.
Tamlyn
5

Proposal lain sedikit samar, jadi saya pikir saya akan berkontribusi:

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
sumber
5

Anda dapat menggunakan perpustakaan yang tersedia di npm, yang menyederhanakan proses ini. 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
sumber
1
Terima kasih telah menyampaikan ini pada kami. Terlihat sangat bermanfaat.
nevf
Dan inilah mengapa setiap proyek menyuntikkan 10.000 dependensi :-).
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

Ini banyak kode bila dibandingkan dengan cara yang lebih sederhana evaluntuk melakukannya, tetapi seperti yang dikatakan Simon Willison, Anda sebaiknya tidak pernah menggunakan eval .

Juga, JSFiddle .

McKayla
sumber
Hebat, ini sangat menyenangkan dan lebih pendek dari CD Sanchez. Terima kasih.
nevf
nilai (a, 'bbbbb') tidak kembali tidak ditentukan
frumbert
4

Saya telah memperluas jawaban yang elegan dengan ninjagecko sehingga fungsinya menangani referensi gaya bertitik dan / atau array, dan sehingga string kosong menyebabkan objek induk dikembalikan.

Ini dia:

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

Lihat contoh jsFiddle kerja saya di sini: http://jsfiddle.net/sc0ttyd/q7zyd/

Sc0ttyD
sumber
Solusi yang sangat bagus. Hanya ada satu masalah, itu mengasumsikan []notasi selalu untuk array. mungkin ada kunci objek yang diwakili seperti itu juga misalnya obj['some-problem/name'].list[1]Untuk memperbaikinya saya harus memperbarui arr_dereffungsi seperti ini javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
Serkan Yersen
1
Meskipun, saat ini, saya tidak akan melakukan ini. Saya akan menggunakan Lodash: 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
sumber
Terima kasih atas semua tanggapan cepat. Tidak suka solusi eval (). Ini dan pos-pos serupa terlihat paling baik. Namun saya masih memiliki masalah. Saya mencoba mengatur nilai obj.ab = nilai baru. Untuk lebih tepatnya, nilai b adalah fungsi jadi saya harus menggunakan obj.ab (new_value). Fungsi dipanggil tetapi nilainya tidak diatur. Saya pikir ini masalah ruang lingkup tapi saya masih menggali. Saya menyadari ini di luar ruang lingkup pertanyaan aslinya. Kode saya menggunakan Knockout.js dan b adalah ko.observable.
nevf
@nevf: Saya menambahkan fungsi kedua yang menurut saya melakukan apa yang Anda inginkan. Anda dapat menyesuaikannya sesuai dengan keinginan Anda, tergantung pada perilaku yang Anda inginkan (mis. Haruskah ia membuat objek jika tidak ada ?, dll.).
Cristian Sanchez
@nevf Tapi milikku melakukannya dengan satu fungsi. ; D
McKayla
terima kasih atas pembaruan yang dapat saya gunakan. @tylermwashburn - dan terima kasih atas implementasi Anda yang lebih pendek yang juga bermanfaat. Selamat bersenang-senang.
nevf
2

Anda dapat memperoleh nilai anggota objek dengan notasi titik dengan satu baris kode:

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

Jika Anda:

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

Untuk membuatnya sederhana, Anda dapat menulis fungsi seperti ini:

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

Penjelasan:

Konstruktor fungsi membuat objek fungsi baru. Dalam JavaScript setiap fungsi sebenarnya adalah objek Function. Sintaks untuk membuat fungsi secara eksplisit dengan Function constructor adalah:

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

di mana arguments(arg1 to argN)harus berupa string yang sesuai dengan pengidentifikasi javaScript yang valid dan functionBodymerupakan string yang berisi pernyataan javaScript yang terdiri dari definisi fungsi.

Dalam kasus kami, kami mengambil keuntungan dari fungsi string tubuh untuk mengambil anggota objek dengan notasi titik.

Semoga ini bisa membantu.

Harish Anchu
sumber
Bisakah Anda jelaskan apa yang sedang dilakukan? Dan bagaimana Anda melewatkan 'ab' dll sebagai parameter fungsi?
nevf
1
Saya memberi ini +1 tetapi JSLint memperingatkan bahwa "Function constructor adalah bentuk eval" .
gabe
2

DAPATKAN / SET jawaban yang juga berfungsi dalam bahasa asli reaksi (Anda tidak dapat menetapkan untuk Object.prototypesaat ini):

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

Pemakaian:

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
sumber
1

Saya menyalin berikut dari jawaban Ricardo Tomasi dan dimodifikasi untuk juga membuat sub-objek yang belum ada seperlunya. Ini sedikit kurang efisien (lebihif dan menciptakan objek kosong), tetapi harus cukup bagus.

Juga, itu akan memungkinkan kita untuk melakukan di Object.prop(obj, 'a.b', false)mana kita tidak bisa sebelumnya. Sayangnya, masih belum mengizinkan kami untuk menetapkan undefined... Belum yakin bagaimana cara melakukannya.

/**
 * 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.
sumber
1

Jika Anda ingin mengonversi objek apa pun yang berisi kunci notasi titik menjadi versi yang tersusun dari kunci tersebut, Anda dapat menggunakan ini.


Ini akan mengonversi sesuatu seperti

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

untuk

{
  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
sumber
Itu tidak memproses nilai-nilai dengan "brothers.0.one" seperti JSON.
Karthick
Apakah Anda punya saran untuk memproses JSON yang lebih kompleks dengan koleksi array.?
Karthick
0

Ini kode saya tanpa menggunakan eval. Mudah dimengerti juga.

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
sumber
0

Ya, ditanya 4 tahun yang lalu dan ya, memperluas prototipe basis biasanya bukan ide yang baik, tetapi, jika Anda menyimpan semua ekstensi di satu tempat, mereka mungkin berguna.
Jadi, inilah cara saya untuk melakukan ini.

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

Sekarang Anda bisa mendapatkan properti bersarang di mana saja tanpa mengimpor modul dengan fungsi atau fungsi salin / tempel.

UPD. Contoh:

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

UPD 2. Perpanjangan berikutnya adalah luwak dalam proyek saya. Saya juga pernah membaca bahwa itu mungkin merusak jquery. Jadi, jangan pernah melakukannya dengan cara selanjutnya

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

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

    return result;
};
Michael Kapustey
sumber
0

Ini implementasi saya

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

Implementasi 2 (menggunakan pengurangan array bukan slice)

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

Contoh:

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

itu juga dapat menangani objek di dalam array untuk

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

Ini adalah solusi saya yang diusulkan oleh: ninjagecko

Bagi saya notasi string sederhana tidak cukup, jadi versi di bawah ini mendukung hal-hal seperti:

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
sumber
0

Dengan risiko mengalahkan kuda mati ... Saya menemukan ini paling berguna dalam melintasi objek bersarang untuk referensi di mana Anda berada sehubungan dengan objek dasar atau ke objek serupa dengan struktur yang sama. Untuk itu, ini berguna dengan fungsi traversal objek bersarang. Perhatikan bahwa saya telah menggunakan array untuk menahan path. Akan sepele untuk memodifikasi ini untuk menggunakan jalur string atau array. Perhatikan juga bahwa Anda dapat menetapkan "tidak terdefinisi" ke nilai, tidak seperti beberapa implementasi lainnya.

/*
 * 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
sumber
0

Saya menggunakan kode ini dalam proyek saya

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

Pemakaian:

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

Beberapa tahun kemudian, saya menemukan ini yang menangani lingkup dan array. misalnyaa['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
sumber
-1

Tidak jelas apa pertanyaan Anda. Mengingat objek Anda, obj.a.bakan memberi Anda "2" seperti apa adanya. Jika Anda ingin memanipulasi string untuk menggunakan tanda kurung, Anda bisa melakukan ini:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
Mark Eirich
sumber
1
Ini tidak berhasil a.b.c, dan tidak benar-benar mencapai apa yang mereka inginkan .. Mereka menginginkan nilai, bukan evaljalan.
McKayla
Saya sekarang memperbaikinya sehingga berfungsi a.b.c, tetapi Anda benar, tampaknya dia ingin mendapatkan / mengatur nilai properti obj.a.b. Pertanyaan itu membingungkan saya, karena dia mengatakan dia ingin "mengubah string" ....
Mark Eirich
Kerja bagus. :) Itu sedikit kabur. Anda melakukan pekerjaan konversi dengan baik.
McKayla
-1

Inilah 10 sen saya untuk kasing, fungsi di bawah ini akan mendapatkan / set berdasarkan jalur yang disediakan, .. yakin Anda dapat memperbaikinya, hapus || dan menggantinya dengan Object.hasOwnPropertyjika Anda benar-benar peduli dengan nilai-nilai yang salah,

saya mengujinya dengan a.b.cdan ab2.c{a:{b:[0,1,{c:7}]}} dan berfungsi untuk pengaturan dan mendapatkan :).

bersorak

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
sumber