Chuyển đổi chuỗi JavaScript theo ký hiệu dấu chấm thành tham chiếu đối tượng


214

Đưa ra một đối tượng JS

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

và một chuỗi

"a.b"

Làm thế nào tôi có thể chuyển đổi chuỗi thành ký hiệu chấm để tôi có thể đi

var val = obj.a.b

Nếu chuỗi chỉ là 'a', tôi có thể sử dụng obj[a]. Nhưng điều này phức tạp hơn. Tôi tưởng tượng có một số phương pháp đơn giản nhưng hiện tại nó thoát ra.


25
@Andrey evallà ác; đừng sử dụng nó
kevinji

5
FYI: Dưới đây là một số bài kiểm tra tốc độ thú vị tôi vừa thực hiện: jsperf.com/dereference-object-property-path-from-opes
James Wilkins

nếu perf là ​​một sự cân nhắc nghiêm túc và bạn đang sử dụng lại cùng một đường dẫn (ví dụ: bên trong hàm lọc mảng), hãy sử dụng hàm tạo Hàm như được mô tả trong câu trả lời của tôi dưới đây. Khi cùng một đường dẫn được sử dụng hàng ngàn lần, phương thức Hàm có thể nhanh hơn gấp 10 lần so với việc trốn tránh hoặc tách và giảm đường dẫn trên mọi quy tắc.
Kevin Crumley


Câu trả lời:


437

lưu ý gần đây: Trong khi tôi hãnh diện rằng câu trả lời này đã nhận được nhiều sự ủng hộ, tôi cũng hơi kinh hoàng. Nếu một người cần chuyển đổi các chuỗi ký hiệu dấu chấm như "xabc" thành các tham chiếu, thì đó có thể (có thể) là một dấu hiệu cho thấy có điều gì đó rất sai đang xảy ra (trừ khi bạn có thể thực hiện một số khử tuần tự lạ).

Điều đó có nghĩa là, những người mới tìm được câu trả lời này phải tự hỏi mình câu hỏi "tại sao tôi lại làm điều này?"

Tất nhiên nói chung là tốt để làm điều này nếu trường hợp sử dụng của bạn nhỏ và bạn sẽ không gặp phải các vấn đề về hiệu suất, VÀ bạn sẽ không cần phải xây dựng sự trừu tượng của mình để làm cho nó phức tạp hơn sau này. Trong thực tế, nếu điều này sẽ làm giảm độ phức tạp của mã và giữ mọi thứ đơn giản, có lẽ bạn nên tiếp tục và làm những gì OP đang yêu cầu. Tuy nhiên, nếu đó không phải là trường hợp, hãy xem xét nếu bất kỳ trong số này áp dụng:

trường hợp 1 : Là phương pháp chính để làm việc với dữ liệu của bạn (ví dụ như hình thức chuyển đối tượng mặc định của ứng dụng xung quanh và hủy bỏ chúng). Giống như hỏi "làm thế nào tôi có thể tra cứu một hàm hoặc tên biến từ một chuỗi".

  • Đây là thực tiễn lập trình xấu (cụ thể là siêu lập trình không cần thiết, và loại vi phạm kiểu mã hóa không có hiệu ứng phụ, và sẽ có hiệu năng đạt được). Thay vào đó, những người mới thấy mình trong trường hợp này, thay vào đó nên xem xét làm việc với các biểu diễn mảng, ví dụ ['x', 'a', 'b', 'c'] hoặc thậm chí một cái gì đó trực tiếp / đơn giản / đơn giản hơn nếu có thể: như không bị mất theo dõi các tham chiếu ở vị trí đầu tiên (lý tưởng nhất nếu chỉ ở phía máy khách hoặc chỉ phía máy chủ), v.v. (Một id duy nhất tồn tại trước đó sẽ không phù hợp để thêm, nhưng có thể được sử dụng nếu thông số khác yêu cầu tồn tại bất kể.)

trường hợp 2 : Làm việc với dữ liệu tuần tự hoặc dữ liệu sẽ được hiển thị cho người dùng. Giống như sử dụng một ngày như một chuỗi "1999-12-30" chứ không phải là một đối tượng Ngày (có thể gây ra lỗi múi giờ hoặc thêm độ phức tạp nối tiếp nếu không cẩn thận). Hoặc bạn biết những gì bạn đang làm.

  • Điều này có thể tốt. Hãy cẩn thận rằng không có chuỗi dấu chấm "." trong các mảnh đầu vào khử trùng của bạn.

Nếu bạn thấy mình sử dụng câu trả lời này mọi lúc và chuyển đổi qua lại giữa chuỗi và mảng, bạn có thể ở trong trường hợp xấu và nên xem xét một giải pháp thay thế.

Đây là một lớp lót thanh lịch ngắn hơn 10 lần so với các giải pháp khác:

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

[sửa] Hoặc trong ECMAScript 6:

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

(Không phải tôi nghĩ eval luôn xấu như những người khác đề xuất (mặc dù nó thường như vậy), tuy nhiên những người đó sẽ hài lòng vì phương pháp này không sử dụng eval. Ở trên sẽ tìm thấy obj.a.b.etcđược đưa ra objvà chuỗi "a.b.etc".)

Để đáp ứng với những người vẫn còn sợ sử dụng reducemặc dù nó nằm trong tiêu chuẩn ECMA-262 (phiên bản thứ 5), đây là một triển khai đệ quy hai dòng:

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

Tùy thuộc vào các tối ưu hóa mà trình biên dịch JS đang thực hiện, bạn có thể muốn đảm bảo mọi hàm lồng nhau không được định nghĩa lại trên mỗi cuộc gọi thông qua các phương thức thông thường (đặt chúng trong một không gian tên đóng, đối tượng hoặc toàn cục).

chỉnh sửa :

Để trả lời một câu hỏi thú vị trong các ý kiến:

Làm thế nào bạn sẽ biến điều này thành một setter là tốt? Không chỉ trả về các giá trị theo đường dẫn, mà còn thiết lập chúng nếu một giá trị mới được gửi vào hàm? - Swader ngày 28 tháng 6 lúc 21:42

(sidenote: thật đáng buồn khi không thể trả lại một đối tượng bằng Setter, vì điều đó sẽ vi phạm quy ước gọi điện, bình luận viên dường như thay vào đó đề cập đến một chức năng kiểu setter chung với các hiệu ứng phụ như index(obj,"a.b.etc", value)làm obj.a.b.etc = value.)

Các reducephong cách là không thực sự phù hợp với điều đó, nhưng chúng ta có thể thay đổi việc thực hiện đệ quy:

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

Bản giới thiệu:

> 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

... mặc dù cá nhân tôi khuyên bạn nên tạo một chức năng riêng biệt setIndex(...). Tôi muốn kết thúc một lưu ý phụ rằng người đặt câu hỏi ban đầu có thể (nên?) Đang làm việc với các mảng chỉ số (mà họ có thể nhận được từ .split), thay vì chuỗi; mặc dù thường không có gì sai với chức năng tiện lợi.


Một bình luận hỏi:

Còn mảng thì sao? một cái gì đó như "ab [4] .cd [1] [2] [3]"? CấmAlexS

Javascript là một ngôn ngữ rất kỳ lạ; trong các đối tượng chung chỉ có thể có các chuỗi làm khóa thuộc tính của chúng, vì vậy, ví dụ nếu xlà một đối tượng chung chung x={}, thì x[1]sẽ trở thành x["1"]... bạn đọc đúng ... yup ...

Mảng Javascript (vốn là phiên bản của Object) đặc biệt khuyến khích các khóa số nguyên, mặc dù bạn có thể làm một cái gì đó như x=[]; x["puppy"]=5;.

Nhưng nói chung (và có những trường hợp ngoại lệ), x["somestring"]===x.somestring(khi được phép; bạn không thể làm được x.123).

(Hãy nhớ rằng bất kỳ trình biên dịch JS nào bạn đang sử dụng đều có thể chọn, có thể, để biên dịch chúng xuống các biểu diễn saner nếu nó có thể chứng minh rằng nó sẽ không vi phạm thông số kỹ thuật.)

Vì vậy, câu trả lời cho câu hỏi của bạn sẽ phụ thuộc vào việc bạn giả sử những đối tượng đó chỉ chấp nhận số nguyên (do hạn chế trong miền vấn đề của bạn) hay không. Giả sử không. Sau đó, một biểu thức hợp lệ là một nối của một định danh cơ sở cộng với một số .identifiers cộng với một số ["stringindex"]s

Điều này sau đó sẽ tương đương với a["b"][4]["c"]["d"][1][2][3], mặc dù chúng ta có lẽ cũng nên hỗ trợ a.b["c\"validjsstringliteral"][3]. Bạn sẽ phải kiểm tra phần ngữ pháp ecmascript trên chuỗi ký tự chuỗi để xem cách phân tích một chuỗi ký tự hợp lệ. Về mặt kỹ thuật, bạn cũng muốn kiểm tra (không giống như trong câu trả lời đầu tiên của tôi) đó ađịnh danh javascript hợp lệ .

Một câu trả lời đơn giản cho câu hỏi của bạn mặc dù, nếu dây của bạn không chứa dấu phẩy hoặc dấu ngoặc , sẽ chỉ là để phù hợp với chiều dài 1+ chuỗi ký tự không có trong bộ ,hoặc [hoặc ]:

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

Nếu chuỗi của bạn không chứa ký tự thoát hoặc "ký tự và vì Mã định danh là ngôn ngữ con của StringLiterals (tôi nghĩ ???) trước tiên bạn có thể chuyển đổi dấu chấm của mình thành []:

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

Tất nhiên, luôn luôn cẩn thận và không bao giờ tin tưởng vào dữ liệu của bạn. Một số cách xấu để làm điều này có thể hoạt động đối với một số trường hợp sử dụng cũng bao gồm:

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

Chỉnh sửa đặc biệt 2018:

Chúng ta hãy đi vòng tròn đầy đủ và thực hiện giải pháp được lập trình quá mức, kém hiệu quả nhất mà chúng ta có thể đưa ra ... vì lợi ích của hamfist thuần túy cú pháp . Với các đối tượng Proxy ES6! ... Chúng ta cũng hãy xác định một số thuộc tính (imho là tốt và tuyệt vời nhưng) có thể phá vỡ các thư viện được viết không đúng. Có lẽ bạn nên cảnh giác khi sử dụng nó nếu bạn quan tâm đến hiệu suất, sự tỉnh táo (của bạn hoặc của người khác), công việc của bạn, v.v.

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

Bản giới thiệu:

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

Đầu ra:

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

(ghi đè proxy) objHyper ['abc'] là: 1

(bộ ghi đè proxy) objHyper ['abc'] = 3, bây giờ obj là: {"a": {"b": {"c": 3, "d": 2}}}

(đằng sau hậu trường) objHyper là: Proxy {a: {'}}

(phím tắt) obj.H ['abc'] = 4

(phím tắt) obj.H ['abc'] là obj ['a'] ['b'] ['c'] là: 4

ý tưởng không hiệu quả: Bạn có thể sửa đổi những điều trên để gửi dựa trên đối số đầu vào; hoặc sử dụng .match(/[^\]\[.]+/g)phương thức để hỗ trợ obj['keys'].like[3]['this']hoặc nếu instanceof Array, sau đó chỉ chấp nhận một mảng làm đầu vào như thế nào keys = ['a','b','c']; obj.H[keys].


Mỗi đề xuất rằng có thể bạn muốn xử lý các chỉ mục không xác định theo cách kiểu NaN 'mềm hơn' (ví dụ: index({a:{b:{c:...}}}, 'a.x.c')trả về không xác định thay vì chưa gõ TypeError) ...:

1) Điều này có ý nghĩa từ quan điểm "chúng ta nên trả lại không xác định thay vì ném lỗi" trong tình huống chỉ mục 1 chiều ({}) ['eg'] == không xác định, vì vậy "chúng ta nên trả lại không xác định thay vì ném lỗi "trong tình huống N chiều.

2) Điều này không có ý nghĩa từ quan điểm mà chúng tôi đang làm x['a']['x']['c'], điều này sẽ thất bại với TypeError trong ví dụ trên.

Điều đó nói rằng, bạn sẽ làm cho công việc này bằng cách thay thế chức năng khử của bạn bằng:

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

(Bạn có thể thực hiện việc này hiệu quả hơn bằng cách sử dụng vòng lặp for và ngắt / trả lại bất cứ khi nào phần con mà bạn chỉ mục tiếp theo không xác định được hoặc sử dụng tính năng bắt thử nếu bạn cho rằng những thất bại đó là đủ hiếm.)


4
reducekhông được hỗ trợ trong tất cả các trình duyệt hiện đang sử dụng.
Ricardo Tomasi

14
@Ricardo: Array.reducelà một phần của tiêu chuẩn ECMA-262. Nếu bạn thực sự muốn hỗ trợ các trình duyệt lỗi thời, bạn có thể xác định Array.prototype.reduceviệc triển khai mẫu được đưa ra ở đâu đó (ví dụ: developer.mozilla.org/en/JavaScript/Reference/Global_Objects/ tựa ).
ninjagecko

2
Có nhưng nó đủ dễ để đặt hai dòng vào một hàm. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf

3
Tôi thích ví dụ thanh lịch này, cảm ơn ninjagecko. Tôi đã mở rộng nó để xử lý ký hiệu kiểu mảng, cũng như các chuỗi trống - xem ví dụ của tôi ở đây: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD

2
@ninjagecko bạn sẽ biến cái này thành setter như thế nào? Không chỉ trả về các giá trị theo đường dẫn, mà còn thiết lập chúng nếu một giá trị mới được gửi vào hàm?
Swader

64

Nếu bạn có thể sử dụng lodash , có một chức năng, chính xác là:

_.get (đối tượng, đường dẫn, [defaultValue])

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

4
Lưu ý: _.get(object, path)không phá vỡ nếu không tìm thấy đường dẫn. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)làm. Đối với trường hợp cụ thể của tôi - không phải cho mọi trường hợp - chính xác những gì tôi cần. Cảm ơn!
Ông B.

1
@ Mr.B. phiên bản mới nhất của Lodash có đối số thứ ba, tùy chọn, cho defaultValue. Các _.get()trở về phương pháp giá trị mặc định nếu _.get()quyết tâm undefined, vì vậy đặt nó vào bất cứ điều gì bạn muốn và xem cho giá trị mà bạn thiết lập.
steampowered

7
Đối với bất cứ ai tự hỏi, nó cũng hỗ trợ _.set(object, path, value).
Jeffrey Roosendaal

19

Một ví dụ liên quan nhiều hơn với đệ quy.

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


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

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

19

bạn cũng có thể sử dụng lodash.get

Bạn chỉ cần cài đặt gói này (npm i --save lodash.get) và sau đó sử dụng nó như thế này:

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

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

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

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

10

Nếu bạn dự kiến ​​sẽ hủy bỏ cùng một đường dẫn nhiều lần, thì việc xây dựng một hàm cho mỗi đường dẫn ký hiệu chấm thực sự có hiệu suất tốt nhất cho đến nay (mở rộng trên các bài kiểm tra hoàn hảo mà James Wilkins liên kết đến trong các nhận xét ở trên).

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

Sử dụng Hàm xây dựng hàm có một số nhược điểm tương tự như eval () về bảo mật và hiệu suất trong trường hợp xấu nhất, nhưng IMO nó là một công cụ bị sử dụng kém cho các trường hợp bạn cần sự kết hợp giữa tính năng động cao và hiệu suất cao. Tôi sử dụng phương pháp này để xây dựng các hàm lọc mảng và gọi chúng trong một vòng lặp tiêu hóa AngularJS. Các cấu hình của tôi luôn hiển thị bước Array.filter () chỉ mất chưa đến 1ms để lọc và lọc khoảng 2000 đối tượng phức tạp, sử dụng các đường dẫn được xác định động sâu 3-4 cấp.

Tất nhiên, một phương pháp tương tự có thể được sử dụng để tạo các hàm setter:

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

1
nếu bạn cần phải hủy bỏ các đường dẫn giống nhau cách nhau một thời gian dài, liên kết jsperf.com ở trên cho thấy một ví dụ về cách lưu và tra cứu chức năng sau này. Hành động gọi hàm tạo Hàm khá chậm, vì vậy mã hoàn hảo nên ghi nhớ kết quả để tránh lặp lại nếu có thể.
Kevin Crumley


7

Tôi đề nghị tách đường dẫn và lặp lại nó và giảm đối tượng bạn có. Đề xuất này hoạt động với một giá trị mặc định cho các thuộc tính bị thiếu.

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

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


6

Lưu ý nếu bạn đã sử dụng Lodash, bạn có thể sử dụng các chức năng propertyhoặc get:

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

Gạch dưới cũng có một propertychức năng nhưng nó không hỗ trợ ký hiệu dấu chấm.


1
Có vẻ Underscore không hỗ trợ ký hiệu dấu chấm. Trả lời cập nhật.
Tamlyn

5

Các đề xuất khác hơi khó hiểu, vì vậy tôi nghĩ tôi sẽ đóng góp:

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

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

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

5

Bạn có thể sử dụng thư viện có sẵn tại npm, giúp đơn giản hóa quá trình này. https://www.npmjs.com/package/dot-object

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

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

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

// Result: Hi there!

1
Cám ơn bạn đã cho chúng tôi biết về vấn đề này. Trông rất hữu ích.
nevf

Và đây là lý do tại sao mọi dự án đều tiêm 10000 phụ thuộc :-).
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

Đây là rất nhiều mã khi so sánh với evalcách làm đơn giản hơn nhiều , nhưng như Simon Willison nói, bạn không bao giờ nên sử dụng eval .

Ngoài ra, JSFiddle .


Tuyệt vời, điều này làm việc một điều trị và ngắn hơn CD Sanchez. Cảm ơn.
nevf

giá trị (a, 'bbbbb') không trả về không xác định
frumbert

4

Tôi đã mở rộng câu trả lời tao nhã của ninjagecko để hàm xử lý cả các tham chiếu kiểu chấm và / hoặc mảng, và do đó, một chuỗi rỗng làm cho đối tượng cha được trả về.

Ở đây bạn đi:

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

Xem ví dụ jsFiddle làm việc của tôi ở đây: http://jsfiddle.net/sc0ttyd/q7zyd/


Giải pháp thực sự tốt. Chỉ có một vấn đề, nó giả sử []ký hiệu luôn dành cho mảng. có thể có các khóa đối tượng được biểu diễn theo cách đó cũng như ví dụ obj['some-problem/name'].list[1]Để khắc phục điều này, tôi đã phải cập nhật arr_derefchức năng như thế này javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
Serkan Yersen

1
Mặc dù, ngày nay, tôi sẽ không làm điều này. Tôi sẽ sử dụng 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

Cảm ơn tất cả các phản ứng nhanh chóng. Đừng như các giải pháp eval (). Điều này và các bài viết tương tự có vẻ tốt nhất. Tuy nhiên tôi vẫn gặp vấn đề. Tôi đang cố gắng đặt giá trị obj.ab = giá trị mới. Để chính xác giá trị của b là một hàm vì vậy tôi cần sử dụng obj.ab (new_value). Hàm được gọi nhưng giá trị không được đặt. Tôi nghĩ đó là một vấn đề phạm vi nhưng tôi vẫn đang đào. Tôi nhận ra điều này nằm ngoài phạm vi của câu hỏi ban đầu. Mã của tôi đang sử dụng Knockout.js và b là ko.observable.
nevf

@nevf: Tôi đã thêm một chức năng thứ hai mà tôi nghĩ làm những gì bạn muốn. Bạn có thể tùy chỉnh nó theo ý thích tùy thuộc vào hành vi bạn muốn (ví dụ: nó có nên tạo các đối tượng nếu chúng không tồn tại không?, V.v.).
Cristian Sanchez

@nevf Nhưng tôi làm điều đó với một chức năng. ; D
McKayla

cảm ơn về bản cập nhật mà tôi đã có thể sử dụng. @tylermwashburn - và cảm ơn vì sự thực hiện ngắn hơn của bạn cũng có tác dụng đối xử. Có một w / e tuyệt vời tất cả.
nevf

2

Bạn có thể nhận được giá trị của một thành viên đối tượng bằng ký hiệu dấu chấm với một dòng mã:

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

Trong trường hợp của bạn:

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

Để đơn giản, bạn có thể viết một hàm như thế này:

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

Giải trình:

Hàm xây dựng hàm tạo một đối tượng Hàm mới. Trong JavaScript, mọi hàm thực sự là một đối tượng Hàm. Cú pháp để tạo một hàm rõ ràng với hàm tạo là:

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

trong đó arguments(arg1 to argN)phải là một chuỗi tương ứng với một mã định danh javaScript hợp lệ và functionBodylà một chuỗi chứa các câu lệnh javaScript chứa định nghĩa hàm.

Trong trường hợp của chúng tôi, chúng tôi tận dụng lợi thế của thân hàm chuỗi để lấy thành viên đối tượng bằng ký hiệu dấu chấm.

Hy vọng nó giúp.


Bạn có thể giải thích chính xác những gì đang làm? Và làm thế nào bạn sẽ vượt qua 'ab', v.v. như là một tham số hàm?
nevf

1
Tôi đã cho điểm này +1 nhưng JSLint cảnh báo rằng "hàm tạo Hàm là một dạng của eval" .
gabe

2

Câu trả lời GET / SET cũng hoạt động theo phản ứng gốc (bạn không thể gán cho Object.prototypehiện tại):

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

Sử dụng:

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

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

1

Tôi đã sao chép những điều sau đây từ câu trả lời của Ricardo Tomasi và sửa đổi để tạo ra các đối tượng phụ chưa tồn tại khi cần thiết. Đó là một ít hiệu quả hơn (nhiều hơn ifvà tạo ra các đối tượng trống), nhưng sẽ khá tốt.

Ngoài ra, nó sẽ cho phép chúng tôi làm những Object.prop(obj, 'a.b', false)nơi chúng tôi không thể trước đây. Thật không may, nó vẫn không cho phép chúng tôi chỉ định undefined... Không biết phải làm thế nào về điều đó.

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

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

1

Nếu bạn muốn chuyển đổi bất kỳ đối tượng nào chứa các khóa ký hiệu dấu chấm thành phiên bản được sắp xếp theo mảng của các khóa đó, bạn có thể sử dụng đối tượng này.


Điều này sẽ chuyển đổi một cái gì đó như

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

đến

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

}

Nó không xử lý các giá trị với "Brothers.0.one" như JSON.
Karthick

Bạn có gợi ý nào để xử lý JSON phức tạp hơn với bộ sưu tập mảng không?
Karthick

0

Đây là mã của tôi mà không sử dụng eval. Nó dễ hiểu quá.

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

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

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

0

Vâng, nó đã được hỏi 4 năm trước và đúng vậy, việc mở rộng các nguyên mẫu cơ bản thường không phải là ý tưởng hay, nhưng, nếu bạn giữ tất cả các tiện ích mở rộng ở một nơi, chúng có thể hữu ích.
Vì vậy, đây là cách của tôi để làm điều này.

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

Bây giờ bạn sẽ có thể nhận được thuộc tính lồng nhau ở mọi nơi mà không cần nhập mô-đun với chức năng hoặc chức năng sao chép / dán.

CẬP NHẬT. Ví dụ:

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

CẬP NHẬT 2. Phần mở rộng tiếp theo brokes mongoose trong dự án của tôi. Ngoài ra tôi đã đọc rằng nó có thể phá vỡ jquery. Vì vậy, không bao giờ làm điều đó theo cách tiếp theo

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

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

    return result;
};

0

Đây là thực hiện của tôi

Thực hiện 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));
}

Thực hiện 2 (sử dụng giảm mảng thay vì lát)

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

Ví dụ:

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

nó cũng có thể xử lý các đối tượng bên trong mảng như đối với

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

0

Đây là giải pháp mở rộng của tôi được đề xuất bởi: ninjagecko

Đối với tôi ký hiệu chuỗi đơn giản là không đủ, vì vậy phiên bản bên dưới hỗ trợ những thứ như:

chỉ mục (obj, 'data.accounts [0] .address [0] .postcode');

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

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

    var _arrIndex = null;

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

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

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

0

Có nguy cơ đánh bại một con ngựa chết ... Tôi thấy điều này hữu ích nhất trong việc vượt qua các đối tượng lồng nhau để tham chiếu đến nơi bạn liên quan đến đối tượng cơ sở hoặc với một đối tượng tương tự có cùng cấu trúc. Cuối cùng, điều này rất hữu ích với chức năng truyền tải đối tượng lồng nhau. Lưu ý rằng tôi đã sử dụng một mảng để giữ đường dẫn. Sẽ là không đáng kể khi sửa đổi điều này để sử dụng đường dẫn chuỗi hoặc mảng. Cũng lưu ý rằng bạn có thể gán "không xác định" cho giá trị, không giống như một số triển khai khác.

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

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

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


0

Tôi đã sử dụng mã này trong dự án của tôi

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

Sử dụng:

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

0

Vài năm sau, tôi thấy cái này xử lý phạm vi và mảng. ví dụa['b']["c"].d.etc

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

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

  return obj;
}

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

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

-1

Không rõ câu hỏi của bạn là gì. Đưa ra đối tượng của bạn, obj.a.bsẽ cung cấp cho bạn "2" như nó là. Nếu bạn muốn thao tác chuỗi để sử dụng dấu ngoặc, bạn có thể làm điều này:

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

1
Điều này không hiệu quả a.b.cvà không thực sự hoàn thành những gì họ muốn .. Họ muốn giá trị, không phải là một evalcon đường.
McKayla

Bây giờ tôi đã sửa nó để nó hoạt động a.b.c, nhưng bạn nói đúng, rõ ràng anh ta muốn lấy / đặt giá trị của tài sản tại obj.a.b. Câu hỏi làm tôi bối rối, vì anh ta nói rằng anh ta muốn "chuyển đổi chuỗi" ....
Mark Eirich

Làm tốt lắm. :) Đó là một chút mơ hồ. Bạn đã làm một công việc tốt của chuyển đổi mặc dù.
McKayla

-1

Đây là 10 xu của tôi, trường hợp dưới đây sẽ nhận / thiết lập dựa trên đường dẫn được cung cấp, .. chắc chắn bạn có thể cải thiện nó, loại bỏ | | và thay thế nó bằng Object.hasOwnPropertynếu bạn quan tâm đến các giá trị sai,

tôi đã thử nghiệm nó với a.b.cvà ab2.c {a:{b:[0,1,{c:7}]}}và các công việc của nó cho cả cài đặt và nhận :).

cổ vũ

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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.