Mengakses objek JavaScript yang tersarang dan array melalui jalur string

454

Saya memiliki struktur data seperti ini:

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

Dan saya ingin mengakses data menggunakan variabel ini:

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

part1name harus diisi dengan someObject.part1.namenilai, yaitu "Bagian 1". Hal yang sama dengan part2quantity yang diisi dengan 60.

Apakah ada cara untuk mencapai ini dengan javascript murni atau JQuery?

Komaruloh
sumber
Tidak yakin apa yang Anda tanyakan di sini? Anda ingin dapat melakukan query part1.name dan mengembalikan teks "part1.name"? Atau Anda ingin cara untuk mendapatkan nilai yang disimpan dalam part1.name?
BonyT
pernahkah Anda mencoba melakukan seperti var part1name = someObject.part1name;`
Rafay
1
@BonyT: Saya ingin menanyakan someObject.part1.name dan mengembalikan nilainya ("Bagian 1"). Namun, saya ingin kueri (saya menyebutnya "kunci") disimpan dalam variabel 'part1name'. Terima kasih untuk balasan Anda. @ 3nigma: Tentu saja saya lakukan. Tapi itu bukan niat saya. Terima kasih balasannya.
Komaruloh
1
di duplikat jawaban, aku suka fyr ini jawaban stackoverflow.com/questions/8817394/...
Steve Hitam

Jawaban:

520

Saya baru saja membuat ini berdasarkan pada beberapa kode serupa yang sudah saya miliki, tampaknya berfungsi:

Object.byString = function(o, s) {
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');
    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
        } else {
            return;
        }
    }
    return o;
}

Pemakaian::

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

Lihat demo yang berfungsi di http://jsfiddle.net/alnitak/hEsys/

EDIT beberapa telah memperhatikan bahwa kode ini akan menimbulkan kesalahan jika melewati string di mana indeks paling kiri tidak sesuai dengan entri bersarang dengan benar dalam objek. Ini adalah masalah yang valid, tetapi IMHO paling baik diatasi dengan try / catchblok saat memanggil, daripada meminta fungsi ini diam-diam mengembalikan undefinedindeks yang tidak valid.

Alnitak
sumber
19
Ini bekerja dengan indah. Silakan berkontribusi ini ke internet dengan membungkusnya sebagai paket simpul.
t3dodson
14
@ t3dodson Saya baru saja melakukannya: github.com/capaj/object-resolve-path hanya perlu diketahui bahwa ini tidak bermain bagus ketika nama properti Anda mengandung '[]' dalam dirinya sendiri. Regex akan menggantinya dengan '.' dan itu tidak berfungsi seperti yang diharapkan
Capaj
20
hal-hal hebat; menggunakan perpustakaan lodash, kita juga bisa melakukan:_.get(object, nestedPropertyString);
ian
17
Ini mungkin akan hilang di lautan komentar, namun kesalahan jika Anda mencoba dan mengatasi properti yang tidak ada. Jadi 'part3[0].name.iDontExist'. Menambahkan tanda centang untuk melihat apakah oada objek dalam if inperbaikan masalah. (Bagaimana Anda melakukannya, itu terserah Anda). Lihat biola diperbarui: jsfiddle.net/hEsys/418
ste2425
2
Ini sangat emas. Kami memiliki aplikasi berbasis konfigurasi dan ini agak membantu! Terima kasih!
Christian Esperar
182

Ini adalah solusi yang saya gunakan:

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

Contoh penggunaan:

// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)

// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'

// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})

// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42

// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42

Keterbatasan:

  • Tidak dapat menggunakan tanda kurung ( []) untuk indeks array — meskipun menentukan indeks array antara token pemisah (misalnya, .) berfungsi dengan baik seperti yang ditunjukkan di atas.
speigg
sumber
7
menggunakan pengurangan adalah solusi yang sangat baik (seseorang juga dapat menggunakan _.reduce()dari garis bawah atau perpustakaan lodash)
Alp
3
Saya pikir selfmungkin tidak terdefinisi di sini. Apakah yang Anda maksud this?
Platinum Azure
2
Inilah pelengkap saya untuk menetapkan nilai menurut jalur: pastebin.com/jDp5sKT9
mroach
1
Adakah yang tahu cara port ini ke TypeScript?
Adam Plocher
1
@ SC1000 ide bagus. Jawaban ini ditulis sebelum parameter default tersedia di sebagian besar browser. Saya akan memperbaruinya ke "function resol (path, obj = self)", karena merujuk objek global sebagai default disengaja.
speigg
179

Ini sekarang didukung oleh penggunaan Lodash _.get(obj, property). Lihat https://lodash.com/docs#get

Contoh dari dokumen:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// → 3

_.get(object, ['a', '0', 'b', 'c']);
// → 3

_.get(object, 'a.b.c', 'default');
// → 'default'
Ian Walker-Sperber
sumber
9
Ini harus menjadi satu-satunya jawaban yang diterima, karena ini adalah satu-satunya jawaban yang berfungsi untuk sintaksasi titik dan braket dan Tidak gagal, ketika kita memiliki '[]' dalam string kunci di path.
Capaj
7
Ini. Plus, ini mendukung_.set(...)
Josh C.
Apa yang terjadi jika benda itu tidak ditemukan?
DDave
@Dave jika nilai yang diteruskan sebagai objek tidak ditentukan atau bukan objek, _.getakan menunjukkan perilaku yang sama seperti ketika tidak ada kunci yang ditemukan di objek yang disediakan. misalnya _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Namun perilaku ini tidak didefinisikan dalam dokumen sehingga dapat berubah.
Ian Walker-Sperber
5
@Capaj kamu bercanda? Dan siapa yang tidak mau / tidak bisa menggunakan lodash?
Andre Figueiredo
74

ES6 : Hanya satu baris di Vanila JS (ini mengembalikan nol jika tidak menemukan alih-alih memberikan kesalahan):

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

Atau contoh:

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

Dengan operator rantai opsional :

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

Untuk fungsi siap pakai yang juga mengenali angka false, 0 dan negatif dan menerima nilai default sebagai parameter:

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

Contoh untuk digunakan:

resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1

Bonus :

Untuk mengatur jalur (Diminta oleh @ rob-gordon) Anda dapat menggunakan:

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

Contoh:

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

Array akses dengan [] :

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

Contoh:

const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1
Adriano Spadoni
sumber
2
Saya suka teknik ini. Ini benar-benar berantakan tetapi saya ingin menggunakan teknik ini untuk tugas. let o = {a:{b:{c:1}}}; let str = 'a.b.c'; str.split('.').splice(0, str.split('.').length - 1).reduce((p,c)=>p&&p[c]||null, o)[str.split('.').slice(-1)] = "some new value";
rob-gordon
1
Saya suka gagasan menggunakan kurangi tetapi logika Anda tampaknya tidak cocok 0, undefineddan nullnilainya. {a:{b:{c:0}}}mengembalikan nullbukan 0. Mungkin secara eksplisit memeriksa nol atau tidak terdefinisi akan menyelesaikan masalah ini. (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku
Hai @SmujMaiku, fungsi "siap digunakan" kembali dengan benar untuk '0', 'tidak terdefinisi' dan 'null', saya baru saja menguji konsol: resolPath ({a: {b: {c: 0}}}, ' abc ', null) => 0; Ia memeriksa apakah kuncinya ada alih-alih nilai itu sendiri yang menghindari lebih dari satu cek
Adriano Spadoni
di sini defaultValue tidak berfungsi, menggunakan Reflect.has(o, k) ? ...(ES6 Reflect.has ) bekerja
Andre Figueiredo
setPathakan menetapkan nilai pada kemunculan pertama pemilih terakhir. Misalnya let o = {}; setPath(o, 'a.a', 0)menghasilkan {a: 0}daripada {a: {a: 0}}. lihat posting ini untuk solusinya.
pkfm
62

Anda harus mengurai senar sendiri:

function getProperty(obj, prop) {
    var parts = prop.split('.');

    if (Array.isArray(parts)) {
        var last = parts.pop(),
        l = parts.length,
        i = 1,
        current = parts[0];

        while((obj = obj[current]) && i < l) {
            current = parts[i];
            i++;
        }

        if(obj) {
            return obj[last];
        }
    } else {
        throw 'parts is not valid array';
    }
}

Ini mengharuskan Anda juga mendefinisikan indeks array dengan notasi titik:

var part3name1 = "part3.0.name";

Itu membuat parsing lebih mudah.

DEMO

Felix Kling
sumber
@ Felix Kling: Solusi Anda memang memberi saya apa yang saya butuhkan. Dan saya berterima kasih banyak untuk itu. Tetapi Alnitak juga menyediakan cara yang berbeda dan tampaknya berhasil juga. Karena saya hanya dapat memilih satu jawaban, saya akan memilih jawaban Alnitak. Bukan berarti solusinya lebih baik daripada Anda atau sesuatu seperti itu. Bagaimanapun, saya sangat menghargai solusi dan upaya yang Anda berikan.
Komaruloh
1
@Komaruloh: Oh saya pikir Anda selalu bisa menjawab suara pada pertanyaan Anda sendiri .... lagi pula saya lebih atau kurang bercanda, saya tidak perlu lebih banyak reputasi;) Selamat coding!
Felix Kling
1
@Felix Kling: Anda membutuhkan setidaknya 15 reputasi untuk memilih. :) Saya percaya Anda tidak perlu lebih banyak reputasi dengan 69k +. Terima kasih
Komaruloh
@Felix FWIW - mengkonversi dari []sintaks ke sintaksis properti cukup sepele.
Alnitak
4
Jika Anda mengubah loop while ke while (l > 0 && (obj = obj[current]) && i < l)maka kode ini berfungsi untuk string tanpa titik juga.
Snea
39

Berfungsi untuk array / array di dalam objek juga. Bertahan terhadap nilai yang tidak valid.

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

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

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

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

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

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

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

TheZver
sumber
10
Terima kasih, ini adalah jawaban terbaik dan paling performan - jsfiddle.net/Jw8XB/1
Dominic
@Endless, saya ingin menekankan jalan harus memisahkan item dengan titik-titik. Kawat gigi tidak akan bekerja. Yaitu untuk mengakses item pertama dalam penggunaan array "0.sp ace".
TheZver
26

menggunakan eval:

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

bungkus untuk mengembalikan yang tidak ditentukan pada kesalahan

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

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

Silakan gunakan akal sehat dan kehati-hatian ketika menggunakan kekuatan eval. Ini agak seperti pedang cahaya, jika Anda menyalakannya ada kemungkinan 90% Anda akan memutuskan anggota tubuh. Ini bukan untuk semua orang.

Shanimal
sumber
7
Apakah eval atau tidak merupakan ide yang baik tergantung pada dari mana data string properti berasal. Saya ragu Anda memiliki alasan untuk khawatir peretas menerobos melalui statis "var p = 'abc'; eval (p);" ketik panggilan. Itu ide yang sangat bagus untuk itu.
James Wilkins
17

Anda dapat mengatur untuk mendapatkan nilai anggota objek yang dalam dengan notasi titik tanpa pustaka JavaScript eksternal dengan trik sederhana berikut ini:

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

Dalam kasus Anda untuk memperoleh nilai part1.namedari someObjecthanya melakukan:

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

Berikut ini adalah demo biola sederhana: https://jsfiddle.net/harishanchu/oq5esowf/

Harish Anchu
sumber
3
function deep_value (obj, path) {mengembalikan Function baru ('o', 'return o.' + path) (obj); }
ArcangelZith
14

Ini mungkin tidak akan pernah melihat cahaya hari ... tapi ini dia.

  1. Menggantikan [] sintaks braket dengan.
  2. Berpisah pada .karakter
  3. Hapus string kosong
  4. Temukan jalurnya (jika tidak undefined)

// "one liner" (ES6)

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

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

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

Nick Grealy
sumber
11

Ini satu liner dengan lodash.

const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"

Atau bahkan lebih baik ...

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

Atau versi ES6 dengan / ...

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

Plunkr

James
sumber
7

Saya pikir Anda meminta ini:

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

Anda bisa meminta ini:

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

Keduanya akan bekerja


Atau mungkin Anda meminta ini

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

var part1name = someObject[partName][nameStr];

Akhirnya Anda bisa meminta ini

var partName = "part1.name";

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

var part1name = someObject[partBits[0]][partBits[1]];
Hogan
sumber
Saya pikir OP meminta solusi terakhir. Namun, string tidak memiliki Splitmetode, melainkan split.
duri
Sebenarnya saya bertanya yang terakhir. Variabel partName diisi dengan string yang menunjukkan struktur kunci ke nilai. Solusi Anda tampaknya masuk akal. Namun saya mungkin perlu mengubah kedalaman data, seperti level 4-5 dan banyak lagi. Dan saya bertanya-tanya apakah saya dapat memperlakukan array dan objek secara seragam dengan ini?
Komaruloh
7

Di sini saya menawarkan lebih banyak cara, yang tampaknya lebih cepat dalam banyak hal:

Opsi 1: Split string aktif. atau [atau] atau 'atau ", balikkan, lewati item kosong.

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
    while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
    return origin;
}

Opsi 2 (kecuali yang tercepat eval): Pemindaian karakter tingkat rendah (tanpa regex / split / dll, hanya pemindaian karakter cepat). Catatan: Yang ini tidak mendukung kutipan untuk indeks.

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var c = '', pc, i = 0, n = path.length, name = '';
    if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
    if (i==n+2) throw "Invalid path: "+path;
    return origin;
} // (around 1,000,000+/- ops/sec)

Opsi 3: ( baru : opsi 2 diperluas untuk mendukung kutipan - sedikit lebih lambat, tetapi masih cepat)

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var c, pc, i = 0, n = path.length, name = '', q;
    while (i<=n)
        ((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
    if (i==n+2 || name) throw "Invalid path: "+path;
    return origin;
}

JSPerf: http://jsperf.com/ways-to-dereference-a-delimited-property-string/3

"eval (...)" masih tetap raja (kinerja bijaksana yaitu). Jika Anda memiliki jalur properti langsung di bawah kendali Anda, seharusnya tidak ada masalah dengan menggunakan 'eval' (terutama jika kecepatan diinginkan). Jika menarik jalur properti "di atas kabel" ( di telepon !? Lol: P), maka ya, gunakan sesuatu yang lain agar aman. Hanya seorang idiot yang akan mengatakan untuk tidak pernah menggunakan "eval" sama sekali, karena ADA alasan bagus untuk menggunakannya. Juga, "Ini digunakan dalam parser JSON Doug Crockford ." Jika inputnya aman, maka tidak ada masalah sama sekali. Gunakan alat yang tepat untuk pekerjaan yang tepat, itu saja.

James Wilkins
sumber
6

Untuk jaga-jaga, siapa pun yang mengunjungi pertanyaan ini pada 2017 atau lebih baru dan mencari cara yang mudah diingat , berikut ini adalah posting blog yang rumit tentang Mengakses Objek Bersarang di JavaScript tanpa dikacaukan oleh

Tidak dapat membaca properti 'foo' yang tidak terdefinisi kesalahan yang

Akses Objek Bersarang Menggunakan Array Reduce

Mari kita ambil contoh struktur ini

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

Untuk dapat mengakses array bersarang, Anda dapat menulis util pengurangan array Anda sendiri.

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
}

// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);

// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.

Ada juga jenis penanganan perpustakaan typy yang sangat baik yang yang melakukan semua ini untuk Anda.

Dengan typy, kode Anda akan terlihat seperti ini

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

Penafian: Saya pembuat paket ini.

Dinesh Pandiyan
sumber
6

AngularJS

Pendekatan Speigg sangat rapi dan bersih, meskipun saya menemukan balasan ini ketika mencari solusi untuk mengakses properti lingkup AngularJS $ melalui jalur string dan dengan sedikit modifikasi ia bekerja:

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

Cukup tempatkan fungsi ini di pengontrol root Anda dan gunakan setiap lingkup anak seperti ini:

$scope.resolve( 'path.to.any.object.in.scope')
nesinervink
sumber
Lihat AngularJS memiliki$scope.$eval cara lain untuk melakukannya dengan AngularJS.
georgeawg
3

Saya belum menemukan paket untuk melakukan semua operasi dengan jalur string, jadi saya akhirnya menulis paket kecil cepat saya sendiri yang mendukung insert (), get () (dengan pengembalian default), set () dan hapus ( ) operasi.

Anda dapat menggunakan notasi titik, tanda kurung, indeks angka, properti nomor string, dan kunci dengan karakter non-kata. Penggunaan sederhana di bawah ini:

> var jsocrud = require('jsocrud');

...

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

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

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

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

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

Bekerja dengan

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

Fungsi sederhana, memungkinkan untuk jalur string atau array.

function get(obj, path) {
  if(typeof path === 'string') path = path.split('.');

  if(path.length === 0) return obj;
  return get(obj[path[0]], path.slice(1));
}

const obj = {a: {b: {c: 'foo'}}};

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

ATAU

console.log(get(obj, ['a', 'b', 'c'])); //foo
Ben
sumber
Jika Anda akan memposting kode sebagai jawaban, tolong jelaskan mengapa kode menjawab pertanyaan.
Tieson T.
2

Ada npmmodul sekarang untuk melakukan ini: https://github.com/erictrinh/safe-access

Contoh penggunaan:

var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
caleb
sumber
2

Meskipun mengurangi baik, saya terkejut tidak ada yang menggunakan forEach:

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

Uji

Flavien Volken
sumber
Anda bahkan tidak memeriksa apakah obj [kunci] benar-benar ada. Itu tidak bisa diandalkan.
Carles Alcolea
@CarlesAlcolea secara default js tidak akan memeriksa apakah kunci objek ada: a.b.cakan memunculkan pengecualian jika tidak ada properti bdi objek Anda. Jika Anda perlu sesuatu secara diam-diam mengabaikan keypath yang salah (yang tidak saya sarankan), Anda masih bisa mengganti forEach dengan yang satu inikeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken
Saya berlari melalui itu objek yang kehilangan penjepit keriting, buruk saya :)
Carles Alcolea
1

Baru saja memiliki pertanyaan yang sama baru-baru ini dan berhasil menggunakan https://npmjs.org/package/tea-properties yang juga setmenyarangkan objek / array:

Dapatkan:

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

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

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

set:

var o = {};

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

assert(o.prop.arr[0].foo, 'bar'); // true
lebih baik
sumber
"Modul ini telah dihentikan. Gunakan chaijs / pathval."
Patrick Fisher
1

Terinspirasi oleh jawaban webjay: https://stackoverflow.com/a/46008856/4110122

Saya membuat fungsi ini yang dapat Anda gunakan untuk Get / Set / Unset nilai apa pun di objek

function Object_Manager(obj, Path, value, Action) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

Untuk menggunakannya:

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

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

 // Unset
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');
Mohamad Hamouday
sumber
1

Saya mengembangkan toko online dengan React. Saya mencoba untuk mengubah nilai dalam objek keadaan disalin untuk memperbarui keadaan asli dengan itu pada saat mengirimkan. Contoh di atas tidak berhasil untuk saya, karena kebanyakan dari mereka bermutasi struktur objek yang disalin. Saya menemukan contoh kerja dari fungsi untuk mengakses dan mengubah nilai properti objek bersarang mendalam: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Ini dia:

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

Berdasarkan jawaban Alnitak .

Saya membungkus polyfill dalam cek, dan mengurangi fungsinya menjadi reduksi berantai tunggal.

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

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

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

Pak Polywhirl
sumber
0

Jika Anda perlu mengakses kunci bersarang yang berbeda tanpa mengetahuinya pada waktu pengkodean (itu akan sepele untuk mengatasinya) Anda dapat menggunakan accessor notasi array:

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

Mereka setara dengan accessor notasi titik dan dapat bervariasi saat runtime, misalnya:

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

var part1name = someObject[part][property];

setara dengan

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

atau

var part1name = someObject.part1.name;

Saya harap ini menjawab pertanyaan Anda ...

EDIT

Saya tidak akan menggunakan string untuk mempertahankan semacam permintaan xpath untuk mengakses nilai objek. Karena Anda harus memanggil fungsi untuk mengurai kueri dan mengambil nilai, saya akan mengikuti jalur lain (tidak:

var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 =  function() { return this.part3[0]['name'];}

// usage: part1name.apply(someObject);

atau, jika Anda tidak nyaman dengan metode yang berlaku

var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 =  function(obj) { return obj.part3[0]['name'];}

// usage: part1name(someObject);

Fungsinya lebih pendek, lebih jelas, penerjemah memeriksa Anda untuk kesalahan sintaks dan sebagainya.

Ngomong-ngomong, saya merasa bahwa tugas sederhana yang dibuat pada waktu yang tepat akan cukup ...

Eineki
sumber
Menarik. Tetapi dalam kasus saya, someObject sudah diinisialisasi ketika saya menetapkan nilai ke part1name. Saya hanya tahu strukturnya. Itu sebabnya saya menggunakan string untuk menggambarkan struktur. Dan berharap bisa menggunakannya untuk menanyakan data saya dari someObject. Terima kasih telah membagikan pemikiran Anda. :)
Komaruloh
@ Kimaruloh: Saya pikir Anda akan menulis bahwa objek TIDAK diinisialisasi saat Anda membuat variabel. Ngomong-ngomong, saya tidak mengerti maksudnya, mengapa Anda tidak dapat melakukan tugas pada waktu yang tepat?
Eineki
Maaf karena tidak menyebutkan bahwa someObject belum diinisialisasi. Adapun alasannya, someObject diambil melalui layanan web. Dan saya ingin memiliki array header yang terdiri dari part1name, part2qty, dll. Sehingga saya bisa mengulang-ulang array header dan mendapatkan nilai yang saya inginkan berdasarkan nilai part1name sebagai 'kunci' / path ke someObject.
Komaruloh
0

Solusi di sini hanya untuk mengakses kunci yang sangat bersarang. Saya membutuhkan satu untuk mengakses, menambahkan, memodifikasi dan menghapus kunci. Inilah yang saya pikirkan:

var deepAccessObject = function(object, path_to_key, type_of_function, value){
    switch(type_of_function){
        //Add key/modify key
        case 0: 
            if(path_to_key.length === 1){
                if(value)
                    object[path_to_key[0]] = value;
                return object[path_to_key[0]];
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    object[path_to_key[0]] = {};
            }
            break;
        //delete key
        case 1:
            if(path_to_key.length === 1){
                delete object[path_to_key[0]];
                return true;
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    return false;
            }
            break;
        default:
            console.log("Wrong type of function");
    }
};
  • path_to_key: path dalam sebuah array. Anda dapat menggantinya dengan string_path.split(".").
  • type_of_function: 0 untuk mengakses (jangan meneruskan nilai apa pun ke value), 0 untuk menambah dan memodifikasi. 1 untuk dihapus.
ayushgp
sumber
0

Membangun dari jawaban Alnitak:

if(!Object.prototype.byString){
  //NEW byString which can update values
Object.prototype.byString = function(s, v, o) {
  var _o = o || this;
      s = s.replace(/\[(\w+)\]/g, '.$1'); // CONVERT INDEXES TO PROPERTIES
      s = s.replace(/^\./, ''); // STRIP A LEADING DOT
      var a = s.split('.'); //ARRAY OF STRINGS SPLIT BY '.'
      for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS
          var k = a[i];
          if (k in _o) {//LOOP THROUGH OBJECT KEYS
              if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED
                if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM
                  if(i === a.length -1){//IF IT'S THE LAST IN THE ARRAY
                    _o[k] = v;
                  }
                }
                _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE
              }
          } else {
              return;
          }
      }
      return _o;
  };

}

Ini memungkinkan Anda untuk menetapkan nilai juga!

Saya telah membuat paket npm dan github dengan ini juga

Tamb
sumber
0

Alih-alih sebuah string, sebuah array dapat digunakan untuk memberi alamat objek dan array seperti: ["my_field", "another_field", 0, "last_field", 10]

Berikut adalah contoh yang akan mengubah bidang berdasarkan representasi array ini. Saya menggunakan sesuatu seperti itu di react.js untuk bidang input terkontrol yang mengubah keadaan struktur bersarang.

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

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

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

Berdasarkan jawaban sebelumnya, saya telah membuat fungsi yang juga dapat menangani tanda kurung. Tapi tidak ada titik di dalamnya karena perpecahan.

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

// (IE9+) Two steps

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

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

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

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

Oboo Cheng
sumber
0

Bekerja dengan Underscore's propertyatau propertyOf:

var test = {
  foo: {
    bar: {
      baz: 'hello'
    }
  }
}
var string = 'foo.bar.baz';


// document.write(_.propertyOf(test)(string.split('.')))

document.write(_.property(string.split('.'))(test));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

Semoga berhasil...

Akash
sumber