Перетворити рядок JavaScript у крапкових позначеннях у посилання на об'єкт


214

Дано об’єкт JS

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

і рядок

"a.b"

як я можу перетворити рядок у крапкові позначення, щоб я міг перейти

var val = obj.a.b

Якби рядок був просто 'a', я міг би скористатися obj[a]. Але це складніше. Я гадаю, що існує якийсь простий метод, але він зараз уникає.


25
@Андрей evalзлий; не користуйтеся ним
kevinji

5
FYI: Ось кілька цікавих тестів на швидкість, які я щойно робив: jsperf.com/dereference-object-property-path-from-string
Джеймс Вілкінс

якщо perf є серйозною проблемою, і ви багато використовуєте ті самі шляхи (наприклад, всередині функції фільтра масиву), використовуйте конструктор функцій, як описано в моїй відповіді нижче. Коли один і той же шлях використовується тисячі разів, метод Функції може бути більш ніж у 10 разів швидшим, ніж нахил або розщеплення та зменшення шляху на кожній дереференції.
Кевін Крамлі


Відповіді:


437

остання зауваження: Хоча я лестощів, що ця відповідь отримала багато відгуків, я також дещо жахнувся. Якщо вам потрібно перетворити рядки точкових нотацій на зразок "xabc" у посилання, це може бути (може бути) ознакою того, що відбувається щось дуже не так (якщо, можливо, ви проводите якусь дивну дезаріалізацію).

Тобто новачки, які знаходять дорогу до цієї відповіді, повинні задати собі питання "чому я це роблю?"

Звичайно, це нормально, якщо це є невеликим випадком використання, і ви не зіткнетесь з проблемами продуктивності, і вам не потрібно буде будувати свою абстракцію, щоб згодом це було складніше. Насправді, якщо це зменшить складність коду та спростить речі, вам, ймовірно, варто йти вперед і робити те, про що вимагає ОП. Однак якщо це не так, врахуйте, чи застосовується будь-яке з них:

випадок 1 : як основний метод роботи з вашими даними (наприклад, за формою програми за замовчуванням для передачі об'єктів навколо них та їх перенаправлення). Як і запитання "як я можу шукати ім'я функції або змінної з рядка".

  • Це погана практика програмування (зайва метапрограмування конкретно, і вид порушує функцію стилю кодування без побічних ефектів і матиме хіти до продуктивності). Новачки, які опиняються в цьому випадку, повинні замість цього розглянути можливість роботи з представленнями масиву, наприклад ['x', 'a', 'b', 'c'], або навіть, якщо можливо, більш прямого / простого / прямого: як не втратити в першу чергу відслідковувати самі посилання (найбільш ідеально, якщо це лише клієнтська або лише серверна) тощо. (Попередньо існуючий унікальний ідентифікатор не було б додати, але міг би бути використаний, якщо специфікація вимагає іншого існування незалежно.)

випадок 2 : Робота з серіалізованими даними або даними, які будуть відображатися користувачеві. Як і використання дати як рядка "1999-12-30", а не об'єкта Date (що може викликати помилки часового поясу або додавати складність серіалізації, якщо не бути обережними). Або ти знаєш, що робиш.

  • Це, можливо, добре. Будьте уважні, щоб не було рядкових точок "." у ваших очищених фрагментах введення.

Якщо ви постійно використовуєте цю відповідь і перетворюєте туди-сюди між рядком і масивом, ви можете опинитися в поганому випадку, і вам слід розглянути альтернативу.

Ось елегантний одноколісний шар, який в 10 разів коротший, ніж інші рішення:

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

[редагувати] Або в ECMAScript 6:

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

(Не те, що я думаю , що Eval завжди погано , як інші вважають , що це (хоча це зазвичай буває), тим не менше , ці люди будуть раді , що цей метод не використовує Eval. Вище буде знайти obj.a.b.etcдані objі рядок "a.b.etc") .

У відповідь на тих, хто все ще боїться використовувати, reduceнезважаючи на те, що він є стандартом ECMA-262 (5-е видання), ось дворядкова рекурсивна реалізація:

function multiIndex(obj,is) {  // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
    return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) {   // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
    return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')

Залежно від оптимізацій, які виконує компілятор JS, ви можете переконатися, що будь-які вкладені функції не будуть визначені заново під час кожного виклику звичайними методами (розміщення їх у закритому, об’єктному або глобальному просторі імен).

редагувати :

Щоб відповісти на цікаве запитання в коментарях:

як би ти перетворив це на сетер також? Не тільки повернення значень шляхом, але і встановлення їх, якщо нове значення буде відправлено у функцію? - Спідник 28 червня о 21:42

(sidenote: на жаль, не може повернути об'єкт із сеттером, оскільки це порушить умову виклику; здається, що коментатор замість цього посилається на загальну функцію стилю сеттера з побічними ефектами на зразок index(obj,"a.b.etc", value)виконання obj.a.b.etc = value.)

reduceСтиль не дуже підходить до цього, але ми можемо змінити рекурсивную реалізацію:

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

Демонстрація:

> 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

... хоча особисто я рекомендую зробити окрему функцію setIndex(...). Я хотів би закінчитись на стороні, зауваживши, що початковий запитувач питання може (повинен?) Працювати з масивами індексів (які вони можуть отримати .split), а не з рядками; хоча зазвичай з функцією зручності немає нічого поганого.


Коментолог запитав:

що з масивами? щось на кшталт "ab [4] .cd [1] [2] [3]"? –AlexS

Javascript - дуже дивна мова; в загальному випадку об'єкти можуть мати лише рядки як їхні властивості, тому, наприклад, якби це xбув загальний об'єкт, як x={}, тоді x[1]вони стали б x["1"]... ви читаєте це право ... так ...

Javascript-масиви (які самі є об'єктами Object) спеціально заохочують цілі клавіші, навіть якщо ви можете зробити щось подібне x=[]; x["puppy"]=5;.

Але загалом (і є винятки) x["somestring"]===x.somestring(коли це дозволено; ви не можете цього зробити x.123).

(Майте на увазі, що будь-який компілятор JS, який ви використовуєте, може вибрати, можливо, скомпонувати їх до більш безпечних уявлень, якщо це може довести, що це не порушило б специфікацію.)

Отже, відповідь на ваше запитання залежатиме від того, ви вважаєте, що ці об'єкти приймають лише цілі числа (через обмеження у вашій проблемній області), чи ні. Припустимо, що ні. Тоді правильним виразом є об'єднання базового ідентифікатора плюс деякий .identifiers плюс деякий ["stringindex"]s

Тоді це було б рівнозначно a["b"][4]["c"]["d"][1][2][3], хоча, мабуть, ми також повинні підтримувати a.b["c\"validjsstringliteral"][3]. Вам доведеться перевірити розділ граматики ecmascript на літеральних рядках, щоб побачити, як розібрати дійсний літеральний рядок. Технічно ви також хочете перевірити (на відміну від моєї першої відповіді), що aце дійсний ідентифікатор JavaScript .

Проста відповідь на ваше запитання , хоча, якщо ваші рядки не містять коми або дужки , буде просто відповідати довжині 1+ послідовності символів , НЕ входять в комплекті ,або [або ]:

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

Якщо ваші рядки не містять символів або "символів , а також тому, що IdentifierNames є підмовою StringLiterals (я думаю ???), ви можете спочатку перетворити свої точки у []:

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

Звичайно, завжди будьте уважні і ніколи не довіряйте своїм даним. Деякі погані способи зробити це, що може працювати в деяких випадках використання, також включають:

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

Спеціальна редакція 2018 року:

Давайте підемо по колу і зробимо найефективніше, жахливо переграмоване запрограмоване рішення, яке ми можемо придумати ... в інтересах синтаксичної чистоти . З об’єктами проксі-сервера ES6! ... Давайте також визначимо деякі властивості, які (imho є прекрасними і чудовими, але) можуть порушити неправильно написані бібліотеки. Можливо, вам слід з обережністю користуватися цим, якщо ви дбаєте про продуктивність, розумність (вашу чи чужі), роботу та ін.

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

Демонстрація:

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

Вихід:

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

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

(набір переопределення проксі) objHyper ['abc'] = 3, тепер obj є: {"a": {"b": {"c": 3, "d": 2}}}

(за лаштунками) objHyper: Проксі {a: {…}}

(ярлик) obj.H ['abc'] = 4

(ярлик) obj.H ['abc'] є obj ['a'] ['b'] ['c']: 4

неефективна ідея: Ви можете змінити вищезазначене для відправки на основі вхідного аргументу; або використовувати .match(/[^\]\[.]+/g)метод для підтримки obj['keys'].like[3]['this'], або якщо instanceof Array, тоді просто прийміть масив як вхідний вид keys = ['a','b','c']; obj.H[keys].


На думку, що, можливо, ви хочете обробляти невизначені індекси "м'якшим" NaN-стилем (наприклад, index({a:{b:{c:...}}}, 'a.x.c')повертайте невизначений, а не uncaught TypeError) ...:

1) Це має сенс з точки зору "ми повинні повернутись невизначеним, а не кидати помилку" в ситуації з 1-мірним індексом ({}) ['напр.'] == undefined, тому "ми повинні повертатися невизначеними, а не кидати помилка "в N-мірній ситуації.

2) Це не має сенсу з точки зору, який ми робимо x['a']['x']['c'], що не вдасться з TypeError у наведеному вище прикладі.

Це означає, що ви зробите цю роботу, замінивши функцію скорочення на будь-яку:

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

(Ви можете зробити це більш ефективним, використовуючи цикл і розбиваючи / повертаючи, коли субрезультат, у який ви наступного індексу, не буде визначений, або використовуючи пробний лов, якщо ви очікуєте, що такі збої будуть досить рідкісними.)


4
reduceпідтримується не у всіх використовуваних браузерах.
Рікардо Томасі

14
@ Ricardo: Array.reduceє частиною стандарту ECMA-262. Якщо ви дійсно хочете підтримати застарілі веб-переглядачі, ви можете визначитись Array.prototype.reduceіз прикладом реалізації цього зразка (наприклад, developer.mozilla.org/en/JavaScript/Reference/Global_Objects/… ).
ninjagecko

2
Так, але досить просто поставити два рядки у функцію. var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf

3
Я люблю цей елегантний приклад, дякую ninjagecko. Я розширив його для обробки позначень у стилі масиву, а також до порожніх рядків - дивіться мій приклад тут: jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD

2
@ninjagecko як ти також перетвориш це на сетер? Не тільки повернення значень шляхом, але і встановлення їх, якщо нове значення буде відправлено у функцію?
Спідник

64

Якщо ви можете використовувати lodash , є функція, яка робить саме це:

_.get (об'єкт, шлях, [defaultValue])

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

4
Примітка: _.get(object, path)не порушується, якщо шлях не знайдено. 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)робить. Для мого конкретного випадку - не для кожного випадку - саме те, що мені було потрібно. Дякую!
Містер Б.

1
@ Mr.B. остання версія Лодаша має третій, необов’язковий аргумент defaultValue. У _.get()методі повертає значення за замовчуванням , якщо _.get()вирішує , щоб undefined, таким чином встановити його на те , що ви хочете , і годин для встановленого значення.
парі

7
Для всіх, хто цікавиться, він також підтримує _.set(object, path, value).
Джеффрі Русендал

19

Трохи більше залучений приклад з рекурсією.

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

ви також можете використовувати lodash.get

Ви просто встановите цей пакет (npm i --save lodash.get) і використовуєте його так:

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

Якщо ви очікуєте, що багато разів відмежуватимете один і той самий шлях, побудова функції для кожного контуру нотацій фактично має найкращу ефективність на сьогоднішній день (розширення на тести парфу, про які Джеймс Вілкінс посилається в коментарях вище).

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

Використання конструктора функцій має деякі ті ж недоліки, що й eval (), що стосується безпеки та найгіршої продуктивності, але IMO - це погано використаний інструмент у випадках, коли потрібна комбінація надзвичайної динамічності та високої продуктивності. Я використовую цю методологію для побудови функцій фільтрів масиву та викликаю їх всередині циклу дайджерів AngularJS. Мої профілі послідовно показують крок array.filter (), який займає менше 1 мс для відновлення і фільтрує близько 2000 складних об'єктів, використовуючи динамічно визначені контури глибиною 3-4 рівня.

Подібна методологія, звичайно, може бути використана для створення функцій сетера:

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

1
якщо вам потрібно тривалий час знеструмлювати одні і ті ж шляхи, посилання jsperf.com вище показує приклад того, як зберегти та шукати функцію пізніше. Дія виклику конструктора функцій відбувається досить повільно, тому код з високим рівнем доступу повинен запам'ятати результати, щоб уникнути його повторення, якщо це можливо.
Кевін Крамлі

9

Багато років з моменту первісної публікації. Зараз є велика бібліотека під назвою 'object-path'. https://github.com/mariocasciaro/object-path

Доступно на NPM та BOWER https://www.npmjs.com/package/object-path

Це так просто, як:

objectPath.get(obj, "a.c.1");  //returns "f"
objectPath.set(obj, "a.j.0.f", "m");

І працює для глибоко вкладених властивостей та масивів.


7

Я пропоную розділити шлях та повторити його та зменшити об'єкт, який у вас є. Ця пропозиція працює зі значенням за замовчуванням для відсутніх властивостей.

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

Зверніть увагу, якщо ви вже використовуєте Lodash, ви можете використовувати propertyабо getфункції:

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

Підкреслення також має propertyфункцію, але вона не підтримує крапки.


1
Здається, підкреслення не підтримує крапки. Відповідь оновлено.
Тамлін

5

Інші пропозиції дещо криптовалютні, тому я подумав, що буду сприяти:

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

Ви можете використовувати бібліотеку, доступну в npm, що спрощує цей процес. 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
Дякуємо, що звернули увагу на це. Виглядає дуже корисно.
nevf

І тому кожен проект вводить 10000 залежностей :-).
Марвін

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

Це багато коду в порівнянні зі значно простішим evalспособом зробити це, але, як каже Саймон Віллісон, ніколи не слід використовувати eval .

Також JSFiddle .


Чудово, це працює частування і коротше, ніж CD Sanchez. Дякую.
nevf

значення (a, 'bbbbb') не повертається невизначеним
frumbert

4

Я розширив елегантну відповідь від ninjagecko, так що функція обробляє і пунктирні, і / або посилання стилю масиву, і таким чином, що порожня рядок викликає повернення батьківського об'єкта.

Ось вам:

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

Дивіться приклад мого робочого jsFiddle тут: http://jsfiddle.net/sc0ttyd/q7zyd/


Дійсно хороше рішення. Є лише одна проблема, вона передбачає, що []позначення завжди є для масивів. там можуть бути представлені об’єктні ключі, наприклад, наприклад, obj['some-problem/name'].list[1]щоб виправити це, мені довелося оновити arr_derefфункцію, як це javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
Serkan Yersen

1
Хоча зараз я б цього не робив. Я б використовував 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

Дякую за всі швидкі відповіді. Не подобаються рішення eval (). Це та подібні пости найкраще виглядають. Однак у мене все ще виникають проблеми. Я намагаюся встановити значення obj.ab = нове значення. Щоб бути точним, значення b - це функція, тому мені потрібно використовувати obj.ab (new_value). Функція викликається, але значення не встановлено. Я думаю, що це питання сфери, але я все ще копаюсь. Я усвідомлюю, що це виходить за межі вихідного питання. Мій код використовує Knockout.js, а b - ко.спостережна.
nevf

@nevf: Я додав другу функцію, яку, на мою думку, робить те, що ти хочеш. Ви можете налаштувати його на свій смак залежно від поведінки, яку ви хочете (наприклад, чи слід створювати об'єкти, якщо їх немає? Тощо).
Крістіан Санчес

@nevf Але моя робить це з однією функцією. ; D
Маккайла

дякую за оновлення, які я зміг використати. @tylermwashburn - і дякуємо за вашу коротшу реалізацію, яка також є приємною пригодою. Майте чудовий ш / е всіх.
nevf

2

Ви можете отримати значення члена об'єкта шляхом позначення крапки в одному рядку коду:

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

У вашому випадку:

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

Щоб зробити це просто, ви можете написати таку функцію:

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

Пояснення:

Конструктор функцій створює новий об'єкт функції. У JavaScript кожна функція є власне об'єктом функції. Синтаксис для створення функції явно за допомогою конструктора функцій:

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

де arguments(arg1 to argN)повинна бути рядок, що відповідає дійсному ідентифікатору javaScript і functionBodyявляє собою рядок, що містить оператори javaScript, що містять визначення функції.

У нашому випадку ми скористаємося тілом функції функції рядка для отримання члена об’єкта з позначенням крапок.

Сподіваюся, це допомагає.


Чи можете ви точно пояснити, що це робить? І як би ви передали "ab" і т.д. в якості параметра функції?
nevf

1
Я дав це +1, але JSLint попереджає, що "конструктор функцій - це форма eval" .
Гейб

2

Отримати відповідь, яка також працює у реальній реакції (ви не можете призначити її Object.prototypeзараз):

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

Використання:

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

Я скопіював наступне з відповіді Рікардо Томасі і змінив, щоб також створити суб’єкти, які ще не існують як потрібні. Це трохи менш ефективно (більше ifs та створення порожніх об'єктів), але має бути досить добре.

Крім того, це дозволить нам робити Object.prop(obj, 'a.b', false)там, де раніше не могли. На жаль, це все ще не дозволить нам призначити undefined... Не впевнені, як це зробити.

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

Якщо ви хочете перетворити будь-який об'єкт, який містить ключі крапкових позначень, у масивну версію цих ключів, ви можете використовувати це.


Це перетворить щось на кшталт

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

до

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

}

Він не обробляв значення з "Brother.0.one", як JSON.
Картік

Чи є якісь пропозиції щодо обробки більш складного JSON з колекцією масивів.?
Картік

0

Ось мій код без використання eval. Його також легко зрозуміти.

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

Так, це було задано 4 роки тому, і так, розширення базових прототипів зазвичай не є гарною ідеєю, але, якщо ви будете тримати всі розширення в одному місці, вони можуть бути корисними.
Отже, ось мій спосіб зробити це.

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

Тепер ви зможете отримати вкладені властивості скрізь без імпорту модуля з функцією чи функцією копіювання / вставки.

Приклад UPD.Example:

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

UPD 2. Наступне розширення вводить мангусту в мій проект. Також я читав, що він може порушити jquery. Отже, ніколи не робіть цього наступним чином

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

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

    return result;
};

0

Ось моя реалізація

Впровадження 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));
}

Реалізація 2 (використовуючи скорочення масиву замість фрагмента)

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

Приклади:

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

він також може обробляти об'єкти всередині масивів, як і раніше

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

0

Це моє розширене рішення, запропоноване: ninjagecko

Для мене простого рядкового позначення було недостатньо, тому нижче версія підтримує такі речі, як:

індекс (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

Загрожуючи побити мертвого коня ... Я вважаю це найбільш корисним у переході вкладених об'єктів, щоб вказати, де ви знаходитесь, щодо базового об'єкта або аналогічного об'єкта з тією ж структурою. З цією метою це корисно за допомогою функції вставки вкладених об'єктів. Зауважте, що я використовував масив для утримання шляху. Було б тривіально модифікувати це, щоб використовувати або шлях рядка, або масив. Також зауважте, що ви можете призначити значення "невизначене", на відміну від деяких інших реалізацій.

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

Я використовував цей код у своєму проекті

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

Використання:

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

0

Через кілька років я виявив, що це обробляє область і масив. напр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

Незрозуміло, у чому ваше запитання. З огляду на ваш об’єкт, obj.a.bдав би вам "2" так само, як є. Якщо ви хочете маніпулювати рядком для використання дужок, ви можете зробити це:

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

1
Це не працює a.b.cі не реально виконує те, що вони хочуть. Вони хочуть значення, а не evalшлях.
Маккейла

Зараз я виправив це, щоб він працював a.b.c, але ви маєте рацію, мабуть, він хотів отримати / встановити вартість майна в obj.a.b. Питання мене бентежило, оскільки він сказав, що хоче "перетворити рядок" ....
Марк Ейріх

Хороша робота. :) Це було трохи невиразно. Ти добре зробив роботу з перетворення.
Маккейла

-1

ось мої 10 копійок на випадок, внизу функція отримає / встановить на основі наданого шляху, .. впевнений, що ви можете покращити її, видалити || і замініть його, Object.hasOwnPropertyякщо ви помиляєтесь на помилкові значення помилково,

Я перевірив це на a.b.cі ab2.c, {a:{b:[0,1,{c:7}]}}і він працює як для налаштування, так і для отримання :).

жир

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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.