การเข้าถึงวัตถุ JavaScript ที่ซ้อนกันและ arays โดยเส้นทางสตริง


456

ฉันมีโครงสร้างข้อมูลเช่นนี้:

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

และฉันต้องการเข้าถึงข้อมูลโดยใช้ตัวแปรเหล่านี้:

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

ควรกรอกsomeObject.part1.nameค่าของpart1name ซึ่งก็คือ "ส่วนที่ 1" สิ่งเดียวกันกับ part2quantity ซึ่งเต็มไปด้วย 60

มีอยู่เพื่อให้บรรลุนี้ด้วยจาวาสคริปต์บริสุทธิ์หรือ JQuery?


ไม่แน่ใจสิ่งที่คุณถามที่นี่? คุณต้องการที่จะสามารถสืบค้น part1.name และส่งคืนข้อความ "part1.name" ได้หรือไม่ หรือคุณต้องการวิธีการรับค่าที่เก็บไว้ใน part1.name
BonyT

คุณลองทำเหมือนvar part1name = someObject.part1name;`
Rafay

1
@BonyT: ฉันต้องการสอบถาม someObject.part1.name และส่งคืนค่าของมัน ("ส่วนที่ 1") อย่างไรก็ตามฉันต้องการแบบสอบถาม (ฉันเรียกมันว่า "กุญแจ") เพื่อเก็บไว้ในตัวแปร 'part1name' ขอบคุณสำหรับการตอบกลับของคุณ. @ 3nigma: ฉันได้ทำอย่างแน่นอน แต่นั่นไม่ใช่ความตั้งใจของฉัน ขอบคุณสำหรับการตอบกลับ.
Komaruloh

1
ในคำตอบที่ซ้ำกันฉันรักคำตอบของ fyr stackoverflow.com/questions/8817394/…
Steve Black

คำตอบ:


520

ฉันเพิ่งทำสิ่งนี้ตามรหัสที่คล้ายกันที่ฉันมีอยู่แล้วดูเหมือนว่าจะใช้งานได้:

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

การใช้งาน ::

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

ดูตัวอย่างการทำงานได้ที่http://jsfiddle.net/alnitak/hEsys/

แก้ไขบางคนสังเกตเห็นว่ารหัสนี้จะโยนข้อผิดพลาดหากผ่านสตริงที่ดัชนีซ้ายสุดไม่สอดคล้องกับรายการซ้อนกันอย่างถูกต้องภายในวัตถุ นี่เป็นข้อกังวลที่ถูกต้อง แต่ IMHO ได้รับการแก้ไขด้วยtry / catchบล็อกที่ดีที่สุดเมื่อโทรแทนที่จะให้ฟังก์ชั่นนี้กลับมาเงียบ ๆundefinedเพื่อหาดัชนีที่ไม่ถูกต้อง


19
มันใช้งานได้อย่างสวยงาม โปรดสนับสนุนสิ่งนี้กับอินเทอร์เน็ตโดยการห่อเป็นแพ็คเกจแพ็คเกจ
t3dodson

14
@ t3dodson ฉันเพิ่งทำไป: github.com/capaj/object-resolve-pathเพิ่งทราบว่าสิ่งนี้เล่นได้ไม่ดีเมื่อชื่อคุณสมบัติของคุณมี '[]' อยู่ในตัว Regex จะแทนที่ด้วย '.' และมันใช้งานไม่ได้ตามที่คาดไว้
Capaj

20
สิ่งที่ดี; ใช้ห้องสมุด lodash อย่างใดอย่างหนึ่งสามารถทำได้:_.get(object, nestedPropertyString);
ian

17
นี่อาจจะหายไปในทะเลของความคิดเห็นอย่างไรก็ตามมันผิดพลาดถ้าคุณลองและที่อยู่สถานที่ให้บริการที่ไม่มีอยู่ 'part3[0].name.iDontExist'ดังนั้น การเพิ่มการตรวจสอบเพื่อดูว่าoเป็นวัตถุในการif inแก้ไขปัญหา (คุณจะทำอย่างไรนั้นขึ้นอยู่กับคุณ) ดูซอที่อัปเดตแล้ว: jsfiddle.net/hEsys/418
ste2425

2
นี่คือทองคำ เรามีแอปพลิเคชั่นที่ใช้ config และนี่เป็นประโยชน์มาก! ขอบคุณ!
Christian Esperar

182

นี่คือทางออกที่ฉันใช้:

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

ตัวอย่างการใช้งาน:

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

ข้อ จำกัด :

  • ไม่สามารถใช้วงเล็บ ( []) สำหรับดัชนีอาร์เรย์ - แม้ว่าการระบุดัชนีอาร์เรย์ระหว่างโทเค็นตัวคั่น (เช่น.) จะทำงานได้ดีดังที่แสดงด้านบน

7
ใช้ลดเป็นทางออกที่ดี (หนึ่งยังสามารถใช้_.reduce()จากขีดหรือไลบรารี lodash)
Alp

3
ฉันคิดว่าselfอาจไม่ได้กำหนดที่นี่ คุณหมายถึงthisอะไร
Platinum Azure

2
นี่คือส่วนเติมเต็มของฉันในการตั้งค่าตามเส้นทาง: pastebin.com/jDp5sKT9
mroach

1
ใครรู้วิธีพอร์ตนี้เพื่อ TypeScript?
Adam Plocher

1
@ SC1000 ความคิดที่ดี คำตอบนี้ถูกเขียนก่อนที่พารามิเตอร์เริ่มต้นจะพร้อมใช้งานในเบราว์เซอร์ส่วนใหญ่ ฉันจะอัปเดตเป็น "การแก้ไขฟังก์ชั่น (path, obj = self)" เนื่องจากการอ้างอิงออบเจ็กต์ทั่วโลกเป็นค่าเริ่มต้นโดยเจตนา
speigg

180

นี้ได้รับการสนับสนุนในขณะนี้โดย lodash _.get(obj, property)ใช้ ดูhttps://lodash.com/docs#get

ตัวอย่างจากเอกสาร:

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

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

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

_.get(object, 'a.b.c', 'default');
// → 'default'

9
นี่ควรเป็นคำตอบเดียวที่ได้รับการยอมรับเพราะนี่เป็นคำตอบเดียวที่ใช้ได้ทั้งกับจุดและไวยากรณ์ของวงเล็บปีกกาและมันจะไม่ล้มเหลวเมื่อเรามี '[]' อยู่ในสตริงของคีย์ในเส้นทาง
Capaj

7
นี้. นอกจากนี้ยังรองรับ_.set(...)
Josh C.

จะเกิดอะไรขึ้นถ้าไม่พบ objet
DDave

@Dave ถ้าค่าที่ส่งเป็นวัตถุนั้นไม่ได้กำหนดหรือไม่วัตถุ_.getจะแสดงพฤติกรรมเช่นเดียวกับเมื่อไม่พบกุญแจในวัตถุที่ให้มา เช่น,_.get(null, "foo") -> undefined _.get(null, "foo", "bar") -> "bar"อย่างไรก็ตามพฤติกรรมนี้ไม่ได้กำหนดไว้ในเอกสารดังนั้นอาจมีการเปลี่ยนแปลง
Ian Walker-Sperber

5
@ Capaj you kiddin 'งั้นเหรอ? และใครไม่ต้องการ / ไม่สามารถใช้ lodash?
Andre Figueiredo

74

ES6 : เพียงหนึ่งบรรทัดใน Vanila JS (จะคืนค่าว่างถ้าไม่พบแทนที่จะให้ข้อผิดพลาด):

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

หรือตัวอย่าง:

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

ด้วยตัวเลือกการโยง :

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

สำหรับฟังก์ชันที่พร้อมใช้งานที่รับรู้จำนวน false, 0 และจำนวนลบและยอมรับค่าเริ่มต้นเป็นพารามิเตอร์:

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

ตัวอย่างการใช้:

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

โบนัส :

ในการตั้งค่าเส้นทาง (ร้องขอโดย @ rob-gordon) คุณสามารถใช้:

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

ตัวอย่าง:

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

เข้าถึงอาร์เรย์ด้วย [] :

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

ตัวอย่าง:

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

2
ฉันรักเทคนิคนี้ มันยุ่งจริงๆ แต่ฉันต้องการใช้เทคนิคนี้ในการมอบหมาย 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
ผมชอบความคิดของการใช้ลด แต่ตรรกะของคุณดูเหมือนจะปิดสำหรับ0, undefinedและnullค่า {a:{b:{c:0}}}ส่งกลับแทนnull 0บางทีการตรวจสอบอย่างชัดเจนสำหรับโมฆะหรือไม่ได้กำหนดจะล้างปัญหาเหล่านี้ (p,c)=>p === undefined || p === null ? undefined : p[c]
SmujMaiku

สวัสดี @SmujMaiku ฟังก์ชัน "พร้อมใช้" ส่งคืนอย่างถูกต้องสำหรับ '0', 'undefined' และ 'null' ฉันเพิ่งทดสอบบนคอนโซล: resolPath ({a: {b: {c: 0}}}, ' abc ', null) => 0; มันตรวจสอบว่ากุญแจมีอยู่แทนค่าตัวเองซึ่งหลีกเลี่ยงการตรวจสอบมากกว่าหนึ่ง
Adriano Spadoni

ค่าเริ่มต้นที่นี่ค่าใช้งานไม่ได้ใช้Reflect.has(o, k) ? ...(ES6 Reflect.has ) ทำงานแม้ว่า
Andre Figueiredo

setPathจะตั้งค่าในการเกิดขึ้นครั้งแรกของตัวเลือกสุดท้าย ตัวอย่างเช่นlet o = {}; setPath(o, 'a.a', 0)ผลในมากกว่า{a: 0} {a: {a: 0}}ดูโพสต์นี้สำหรับการแก้ปัญหา
pkfm

62

คุณต้องแยกสตริงด้วยตัวเอง:

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

สิ่งนี้ต้องการให้คุณกำหนดดัชนีอาร์เรย์ด้วยเครื่องหมายจุด:

var part3name1 = "part3.0.name";

มันทำให้การแยกวิเคราะห์ง่ายขึ้น

การสาธิต


@ Felix Kling: ทางออกของคุณให้สิ่งที่ฉันต้องการ และฉันขอบคุณมากสำหรับสิ่งนั้น แต่อัลนิทักก็ให้วิธีการต่าง ๆ และดูเหมือนจะทำงานด้วยเช่นกัน เนื่องจากฉันเลือกได้เพียงคำตอบเดียวฉันจะเลือกคำตอบ Alnitak ไม่ใช่ว่าทางออกของเขาดีกว่าคุณหรืออะไรทำนองนั้น อย่างไรก็ตามฉันขอขอบคุณทางออกและความพยายามของคุณ
Komaruloh

1
@Komaruloh: โอ้ฉันคิดว่าคุณสามารถโหวตคำตอบสำหรับคำถามของคุณเองได้เสมอ…. อย่างไรก็ตามฉันล้อเล่นมากขึ้นหรือน้อยลงฉันไม่ต้องการชื่อเสียงมากขึ้น) การเข้ารหัสที่มีความสุข!
เฟลิกซ์คลิง

1
@Felix Kling: คุณต้องมีชื่อเสียงอย่างน้อย 15 คะแนนขึ้นไป :) ฉันเชื่อว่าคุณไม่ต้องการชื่อเสียงมากกว่านี้กับ 69k + ขอบคุณ
Komaruloh

@Felix FWIW - การแปลงจาก[]ไวยากรณ์เป็นไวยากรณ์ของคุณสมบัตินั้นค่อนข้างเล็กน้อย
Alnitak

4
หากคุณเปลี่ยนการวนซ้ำ while เป็นwhile (l > 0 && (obj = obj[current]) && i < l)รหัสนี้จะทำงานกับสตริงที่ไม่มีจุดเช่นกัน
Snea

39

ทำงานสำหรับอาร์เรย์ / อาร์เรย์ภายในวัตถุด้วย การป้องกันค่าที่ไม่ถูกต้อง

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

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

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

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

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

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

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


10
ขอบคุณนี่เป็นคำตอบที่ดีที่สุดและยอดเยี่ยมที่สุด - jsfiddle.net/Jw8XB/1
Dominic

@ ไม่สิ้นสุดฉันต้องการเน้นเส้นทางควรแยกรายการด้วยจุด เครื่องมือจัดฟันจะไม่ทำงาน คือเข้าถึงไอเท็มแรกในอาร์เรย์ใช้ "0.sp ace"
TheZver

26

ใช้ eval:

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

wrap เพื่อส่งคืน undefined บนข้อผิดพลาด

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

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

โปรดใช้สามัญสำนึกและข้อควรระวังในการใช้พลังแห่ง eval มันเหมือนกระบี่แสงถ้าคุณเปิดมันมีโอกาส 90% ที่คุณจะตัดขา ไม่ใช่สำหรับทุกคน


7
ความคิดที่ดีหรือไม่ขึ้นอยู่กับว่าข้อมูลสตริงคุณสมบัติมาจากไหน ฉันสงสัยว่าคุณมีเหตุผลใด ๆ ที่ต้องคำนึงถึงแฮ็กเกอร์ที่บุกเข้ามาทางสถิต "var p = 'abc'; eval (p);" ประเภทการโทร มันเป็นความคิดที่ดีอย่างสมบูรณ์แบบสำหรับสิ่งนั้น
James Wilkins

17

คุณสามารถจัดการเพื่อรับค่าของสมาชิกวัตถุลึกด้วยเครื่องหมายจุดโดยไม่มีไลบรารี JavaScript ภายนอกด้วยเคล็ดลับต่อไปนี้:

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

ในกรณีของคุณที่จะได้รับมูลค่าpart1.nameจากsomeObjectเพียงแค่ทำ:

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

นี่คือตัวอย่างซอที่ทำได้ง่าย: https://jsfiddle.net/harishanchu/oq5esowf/


3
ฟังก์ชั่น deep_value (obj, เส้นทาง) {return ฟังก์ชั่นใหม่ ('o', 'return o.' + เส้นทาง) (obj); }
ArcangelZith

14

นี่อาจจะไม่เคยเห็นแสงของวัน ... แต่ที่นี่มันเป็นอย่างไร

  1. แทนที่[]วงเล็บเหลี่ยมด้วย.
  2. แยกบน .ตัวละคร
  3. ลบสตริงว่าง
  4. ค้นหาเส้นทาง (อย่างอื่นundefined)

// "one liner" (ES6)

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

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

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


11

มันเป็นหนึ่งซับกับ 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"

หรือดีกว่า ...

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

หรือ ES6 เวอร์ชันที่มีการลด ...

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

Plunkr


7

ฉันคิดว่าคุณกำลังขอสิ่งนี้:

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

คุณอาจจะขอสิ่งนี้:

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

ทั้งสองอย่างนี้จะได้ผล


หรือคุณอาจจะขอสิ่งนี้

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

var part1name = someObject[partName][nameStr];

ในที่สุดคุณอาจจะขอสิ่งนี้

var partName = "part1.name";

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

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

ฉันคิดว่า OP ขอวิธีแก้ไขล่าสุด แต่สายไม่ได้มีSplitวิธีการ splitแต่
duri

ที่จริงฉันถามคนสุดท้าย ตัวแปร partName ถูกเติมด้วยสตริงที่ระบุโครงสร้างคีย์เป็นค่า วิธีแก้ปัญหาของคุณดูสมเหตุสมผล อย่างไรก็ตามฉันอาจต้องแก้ไขเพื่อเพิ่มความลึกของข้อมูลเช่นระดับ 4-5 ขึ้นไป และฉันสงสัยว่าฉันสามารถจัดการกับอาเรย์และวัตถุอย่างสม่ำเสมอหรือไม่?
Komaruloh

7

ที่นี่ฉันเสนอวิธีการเพิ่มเติมซึ่งดูเร็วขึ้นในหลาย ๆ ด้าน:

ตัวเลือกที่ 1: แยกสตริงบน หรือ [หรือ] หรือ 'หรือ "ย้อนกลับข้ามรายการที่ว่างเปล่า

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

ตัวเลือกที่ 2 (เร็วที่สุดยกเว้นทั้งหมดeval): การสแกนตัวอักษรในระดับต่ำ (ไม่มี regex / split / etc เป็นเพียงการสแกนแบบด่วน) หมายเหตุ: อันนี้ไม่สนับสนุนเครื่องหมายคำพูดสำหรับดัชนี

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)

ตัวเลือก 3: ( ใหม่ : ตัวเลือก 2 ขยายเพื่อรองรับคำพูด - ช้าลงเล็กน้อย แต่ก็ยังเร็ว)

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 (... )" ยังคงเป็นราชาอยู่ (แสดงอย่างชาญฉลาดนั่นคือ) หากคุณมีเส้นทางคุณสมบัติโดยตรงภายใต้การควบคุมของคุณไม่ควรมีปัญหาใด ๆ กับการใช้ 'eval' (โดยเฉพาะอย่างยิ่งถ้าต้องการความเร็ว) หากดึงเส้นทางคุณสมบัติ "เหนือเส้นลวด" ( ที่บรรทัด !? lol: P) ใช่แล้วใช้อย่างอื่นเพื่อความปลอดภัย มีเพียงคนงี่เง่าเท่านั้นที่พูดว่าอย่าใช้ "Eval" เลยเนื่องจากมีเหตุผลที่ดีเมื่อใช้ นอกจากนี้ "มันถูกใช้ในตัวแยกวิเคราะห์ JSON ของ Doug Crockford " หากอินพุตปลอดภัยแล้วก็ไม่มีปัญหาเลย ใช้เครื่องมือที่เหมาะสมสำหรับงานที่ใช่


6

ในกรณีที่ทุกคนกำลังเยี่ยมชมคำถามนี้ในปี 2560 หรือใหม่กว่าและมองหาวิธีที่ง่ายต่อการจดจำนี่คือโพสต์บล็อกที่ซับซ้อนเกี่ยวกับการเข้าถึงวัตถุที่ซ้อนกันใน JavaScriptโดยไม่ต้องถูกบีบอัดด้วย

ไม่สามารถอ่านคุณสมบัติ "foo" จากที่ไม่ได้กำหนดข้อผิดพลาดที่

เข้าถึงวัตถุที่ซ้อนกันโดยใช้การลดแถวลำดับ

ลองมาตัวอย่างโครงสร้างนี้กัน

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

เพื่อให้สามารถเข้าถึงอาร์เรย์ที่ซ้อนกันคุณสามารถเขียน array ลดของคุณเองได้

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.

นอกจากนี้ยังมีประเภทที่ยอดเยี่ยมการจัดการห้องสมุดน้อยที่สุดtypyสำหรับคุณ

เมื่อพิมพ์ผิดรหัสของคุณจะเป็นดังนี้

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

คำเตือน: ฉันเป็นผู้เขียนของแพคเกจนี้


6

AngularJS

แนวทางของ Speigg นั้นเรียบร้อยและสะอาดมากแม้ว่าฉันจะพบคำตอบนี้ในขณะที่ค้นหาวิธีการแก้ปัญหาในการเข้าถึงคุณสมบัติขอบเขต AngularJS $ โดยเส้นทางสตริงและด้วยการปรับเปลี่ยนเล็กน้อยมันทำงาน:

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

เพียงแค่วางฟังก์ชั่นนี้ในตัวควบคุมรูทของคุณและใช้ขอบเขตลูกใด ๆ เช่นนี้

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

ดูAngularJS มี$scope.$evalวิธีอื่นในการดำเนินการกับ AngularJS
georgeawg

3

ฉันยังไม่พบแพ็คเกจที่จะดำเนินการทั้งหมดด้วยเส้นทางสตริงดังนั้นฉันจึงเขียนแพ็คเกจเล็ก ๆ ของตัวเองอย่างรวดเร็วซึ่งรองรับ insert (), get () (ด้วยการคืนค่าเริ่มต้น), set () และลบ ( ) การดำเนินงาน

คุณสามารถใช้เครื่องหมายจุดเครื่องหมายวงเล็บตัวเลขดัชนีคุณสมบัติหมายเลขสตริงและคีย์ที่มีอักขระที่ไม่ใช่คำ ใช้งานง่ายด้านล่าง:

> var jsocrud = require('jsocrud');

...

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

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

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

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


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

ใช้ได้กับ

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

3

ฟังก์ชั่นที่เรียบง่ายช่วยให้ทั้งเส้นทางสตริงหรืออาร์เรย์

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

หรือ

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

หากคุณกำลังจะโพสต์รหัสเป็นคำตอบโปรดอธิบายสาเหตุที่รหัสตอบคำถาม
Tieson T.


2

แม้ว่าการลดจะดี แต่ฉันก็ไม่แปลกใจที่ไม่มีใครใช้สำหรับแต่ละคน:

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

ทดสอบ


คุณยังไม่ได้ตรวจสอบว่ามี obj [key] อยู่จริงหรือไม่ มันไม่น่าเชื่อถือ
Carles Alcolea

@CarlesAlcolea โดยค่าเริ่มต้น js จะไม่ตรวจสอบว่ามีคีย์ของวัตถุอยู่หรือไม่: a.b.cจะเพิ่มข้อยกเว้นหากไม่มีคุณสมบัติbในวัตถุของคุณ หากคุณต้องการบางสิ่งบางอย่างในการยกเลิกการปิดกั้นพา ธ ที่ไม่ถูกต้อง (ซึ่งฉันไม่แนะนำ) คุณยังสามารถแทนที่ forEach ด้วยอันนี้ได้keys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken

ฉันวิ่งผ่านมันเป็นวัตถุที่ไม่มีปีกค้ำยันผมไม่ดี :)
Carles Alcolea

1

เพิ่งมีคำถามเดียวกันเมื่อเร็ว ๆ นี้และใช้https://npmjs.org/package/tea-properties ได้สำเร็จซึ่งยังมีsetวัตถุ / อาร์เรย์ซ้อนอยู่:

จะได้รับ:

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

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

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

ตั้ง:

var o = {};

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

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

"โมดูลนี้ถูกยกเลิกใช้ chaijs / pathval"
แพทริคฟิชเชอ

1

รับแรงบันดาลใจจากคำตอบของ @ webjay: https://stackoverflow.com/a/46008856/4110122

ฉันทำฟังก์ชั่นนี้ซึ่งคุณสามารถใช้เพื่อรับ/ ตั้งค่า / ยกเลิกการตั้งค่าใด ๆ ในวัตถุ

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

วิธีใช้:

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

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

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

1

ฉันกำลังพัฒนาร้านค้าออนไลน์ด้วย React ฉันพยายามเปลี่ยนค่าในวัตถุสถานะที่คัดลอกเพื่ออัปเดตสถานะเดิมเมื่อส่ง ตัวอย่างข้างต้นไม่ได้ผลสำหรับฉันเพราะส่วนใหญ่ของพวกเขากลายพันธุ์โครงสร้างของวัตถุที่คัดลอก ฉันพบตัวอย่างการทำงานของฟังก์ชั่นสำหรับการเข้าถึงและการเปลี่ยนค่าของคุณสมบัติวัตถุที่ซ้อนกันลึก: https://lowrey.me/create-an-object-by-path-in-javascript-2/นี่คือ:

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

1

ตามคำตอบของ Alnitakคำตอบ

ฉันหุ้มโพลีฟิลล์ไว้ในเช็คและลดฟังก์ชั่นเป็นการลดการผูกมัดเดียว

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

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

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


0

หากคุณจำเป็นต้องเข้าถึงคีย์ที่ซ้อนกันที่แตกต่างกันโดยไม่ทราบว่าในเวลาการเข้ารหัส (มันจะเป็นเรื่องเล็กน้อยที่จะจัดการพวกเขา) คุณสามารถใช้ accessoration notation access:

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

พวกเขาจะเทียบเท่ากับตัวบ่งชี้จุดสัญกรณ์และอาจแตกต่างกันที่รันไทม์ตัวอย่างเช่น:

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

var part1name = someObject[part][property];

เทียบเท่ากับ

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

หรือ

var part1name = someObject.part1.name;

ฉันหวังว่าคำถามนี้จะตอบคำถามของคุณ ...

แก้ไข

ฉันจะไม่ใช้สตริงเพื่อ mantain การจัดเรียงของ XPath แบบสอบถามในการเข้าถึงค่าวัตถุ ในขณะที่คุณต้องเรียกใช้ฟังก์ชั่นในการแยกแบบสอบถามและดึงค่าฉันจะไปตามเส้นทางอื่น (ไม่:

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

หรือถ้าคุณไม่สบายใจกับวิธีการสมัคร

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

ฟังก์ชั่นจะสั้นกว่าชัดเจนขึ้นล่ามจะตรวจสอบพวกเขาเพื่อหาข้อผิดพลาดทางไวยากรณ์และอื่น ๆ

โดยวิธีการที่ฉันรู้สึกว่าการมอบหมายงานง่าย ๆ ในเวลาที่เหมาะสมจะพอเพียง ...


น่าสนใจ แต่ในกรณีของฉัน someObject จะเริ่มต้นเมื่อฉันกำหนดค่าให้กับ part1name ฉันเท่านั้นที่รู้โครงสร้าง นั่นคือเหตุผลที่ฉันใช้สตริงเพื่ออธิบายโครงสร้าง และหวังว่าจะสามารถใช้มันเพื่อค้นหาข้อมูลของฉันจาก someObject ขอบคุณที่แบ่งปันความคิดของคุณ :)
โคมารูโลห์

@Komaruloh: ฉันคิดว่าคุณจะเขียนว่าวัตถุยังไม่เริ่มต้นเมื่อคุณสร้างตัวแปรของคุณ โดยวิธีที่ฉันไม่ได้รับจุดทำไมคุณไม่สามารถทำงานที่ได้รับมอบหมายในเวลาที่เหมาะสม?
Eineki

ขออภัยที่ไม่ได้กล่าวถึงว่าอ็อบเจ็กบางตัวยังไม่ได้เริ่มต้น สำหรับเหตุผลบางObjectถูกดึงผ่านบริการเว็บ และฉันต้องการอาร์เรย์ของส่วนหัวซึ่งประกอบด้วย part1name, part2qty ฯลฯ ดังนั้นฉันจึงสามารถวนรอบอาร์เรย์ส่วนหัวและรับค่าที่ฉันต้องการตามค่า part1name เป็น 'key' / path ของ someObject
Komaruloh

0

โซลูชั่นที่นี่เป็นเพียงการเข้าถึงกุญแจที่ซ้อนกันอย่างล้ำลึก ฉันต้องการรหัสสำหรับการเข้าถึงการเพิ่มการแก้ไขและการลบคีย์ นี่คือสิ่งที่ฉันมาด้วย:

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: เส้นทางในอาร์เรย์ string_path.split(".")คุณสามารถเปลี่ยนได้โดยคุณ
  • type_of_function: 0 สำหรับการเข้าถึง (ไม่ผ่านค่าใด ๆ ไปยังvalue), 0 สำหรับการเพิ่มและแก้ไข 1 สำหรับการลบ

0

การสร้างคำตอบของ 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;
  };

}

สิ่งนี้ช่วยให้คุณสามารถตั้งค่าได้เช่นกัน!

ฉันได้สร้างแพคเกจ npmและgithubด้วยสิ่งนี้เช่นกัน


0

แทนที่จะใช้สตริงคุณสามารถใช้อาเรย์ที่อยู่วัตถุและอาร์เรย์ที่ซ้อนกันเช่น: ["my_field", "another_field", 0, "last_field", 10]

นี่คือตัวอย่างที่จะเปลี่ยนฟิลด์ตามการแสดงอาร์เรย์นี้ ฉันกำลังใช้สิ่งเช่นนั้นใน react.js สำหรับเขตข้อมูลอินพุตที่ควบคุมซึ่งเปลี่ยนแปลงสถานะของโครงสร้างที่ซ้อนกัน

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

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

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

0

จากคำตอบก่อนหน้านี้ฉันได้สร้างฟังก์ชั่นที่สามารถจัดการกับวงเล็บได้ แต่ไม่มีจุดภายในเนื่องจากการแยก

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

0

// (IE9+) Two steps

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

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

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

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


0

การทำงานกับUnderscore's propertyหรือ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>

โชคดี...

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.