Доступ до вкладених об’єктів JavaScript та арайів шляхом шляхом рядка


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

part1name має бути заповнене someObject.part1.nameзначенням ', яке є "Частина 1". Те ж саме з part2quantity, яке заповнилося 60.

Чи все-таки можна цього досягти чистим JavaScript або JQuery?


Не впевнені, що ви тут просите? Ви хочете мати можливість запитувати part1.name і повернути текст "part1.name"? Або ви хочете, щоб засіб отримати значення, збережене в part1.name?
BonyT

ви намагалися робити так, як var part1name = someObject.part1name;`
Рафай

1
@BonyT: Я хочу запитати someObject.part1.name і повернути його значення ("Частина 1"). Однак я хочу, щоб запит (я назвав його "ключем") зберігався у змінній "part1name". Дякуємо за Ваш відповідь. @ 3nigma: Я, безумовно, так і роблю. Але це не мій намір. Дякую за відповідь.
Комаруло

1
у дублюючій відповіді, я люблю відповідь Фіра stackoverflow.com/questions/8817394/…
Стів Блек

Відповіді:


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/

EDIT деякі помітили, що цей код видасть помилку, якщо передається рядок, де більшість лівих індексів не відповідають правильно вкладеному запису в об'єкті. Це важлива проблема, але IMHO найкраще вирішувати з try / catchблоком під час виклику, а не з тим, щоб ця функція мовчки поверталасяundefined за недійсним індексом.


19
Це прекрасно працює. Будь ласка, внесіть це в Інтернет, загорнувши його як пакет вузлів.
t3dodson

14
@ t3dodson Я щойно зробив: github.com/capaj/object-resolve-path просто пам’ятайте, що це не дуже добре, коли назва вашого власності містить „[]“ у собі. Regex замінить його на "." і це не працює так, як очікувалося
Capaj

20
чудові речі; з використанням бібліотеки lodash, можна також зробити:_.get(object, nestedPropertyString);
Ян

17
Це, ймовірно, загубиться в морі коментарів, однак воно помилиться, якщо спробувати адресувати властивість, яка не існує. Отже 'part3[0].name.iDontExist'. Додавання чека, щоб побачити, чи oє об’єкт у if inвирішенні проблеми. (Як ви вирішите це вирішувати). Дивіться оновлений скрипт
ste2425

2
Це так золото. У нас є програма на основі конфігурації, і це корисно! Дякую!
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()бібліотекою підкреслення або подання)
Альп

3
Я думаю, selfтут, мабуть, не визначено. Ви маєте на увазі this?
Platinum Azure

2
Ось моє доповнення до встановлення значень за маршрутом: pastebin.com/jDp5sKT9
mroach

1
Хтось знає, як перенести це в TypeScript?
Адам Плочер

1
@ SC1000 хороша ідея. Ця відповідь була написана до того, як параметри за замовчуванням були доступні у більшості браузерів. Я оновлю його до "function resolution (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(...)
Джош С.

що відбувається, якщо об'єкт не знайдений?
DDave

@DDave, якщо значення, передане як об'єкт, не визначене або не є об'єктом, _.getбуде демонструвати таку ж поведінку, що і тоді, коли в наданому об'єкті не знайдено жодного ключа. наприклад _.get(null, "foo") -> undefined, _.get(null, "foo", "bar") -> "bar". Однак така поведінка не визначена в документах, що підлягають зміні.
Ян Уокер-Спербер

5
@Capaj ви жартуєте? А хто не хоче / не може використовувати лодаш?
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}}})

Для функції готового до використання, яка також розпізнає помилкове, 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", "невизначено" та "null", я щойно перевірив на консолі: resolutionPath ({a: {b: {c: 0}}}, " abc ', null) => 0; Він перевіряє, чи існує ключ замість самого значення, яке уникає більше однієї перевірки
Adriano Spadoni

тут defaultValue не працював, використовуючи 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";

Це полегшує розбір.

DEMO


@Felix Kling: Ваше рішення дає мені те, що мені потрібно. І я дуже дякую за це. Але Альнітак також пропонує різні способи і, здається, працює і так. Оскільки я можу вибрати лише одну відповідь, я оберу відповідь Альнітака. Не те, щоб його рішення було кращим за вас чи щось подібне. У будь-якому разі, я дуже ціную ваше рішення та зусилля, які ви доклали.
Комаруло

1
@Komaruloh: О, я думав, ти завжди можеш відповісти на голосування на своє власне питання .... все одно я більш-менш жартую, більше репутації мені не потрібно;) Щасливе кодування!
Фелікс Клінг

1
@Felix Kling: Вам потрібно принаймні 15 репутації, щоб проголосувати. :) Я вважаю, що вам не потрібно більше репутації з 69k +. Спасибі
Комаруло

@Felix FWIW - перетворення з []синтаксису у синтаксис властивості досить тривіально.
Альнітак

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
Домінік

@ Без кінця, я хочу наголосити, що шлях повинен розділяти елементи крапками. Підтяжки не працюватимуть. Тобто для доступу до першого елемента в масиві використовуйте "0.sp ace".
TheZver

26

за допомогою eval:

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

wrap, щоб повернутись невизначено за помилкою

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

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

Будь ласка, використовуйте здоровий глузд і обережність, коли володієте силою eval. Це трохи схоже на легку шаблю, якщо ввімкнути її, то на 90% шанс ви відірвете кінцівку. Це не для всіх.


7
Від того, чи є eval хорошою ідеєю, залежить від того, звідки беруться дані про рядкові властивості. Сумніваюсь, у вас є якісь підстави бути занепокоєними тим, що хакери пробиваються через статичний "var p = 'abc'; eval (p);" тип дзвінка. Це ідеально чудова ідея для цього.
Джеймс Вілкінс

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, path) {повернути нову функцію ('o', 'return o.' + path) (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

Це один лайнер з лодашем.

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

Плункр


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

Я думаю, що ОП просить останнього рішення. Однак у рядків немає Splitметоду, скоріше split.
дурі

Насправді я запитував останню. Змінна partName заповнена рядком, що вказує на значення структури ключа. Здається, ваше рішення має сенс. Однак мені може знадобитися змінити для збільшення глибини даних, як-от 4-5 рівня і більше. І мені цікаво, чи можу я обробляти масив і рівномірно проти цього?
Комаруло

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): сканування символів низького рівня (відсутність регулярного вираження / розділення / тощо, лише швидке сканування діаграми) Примітка. Цей не підтримує лапок для індексів.

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 Дуга Крокфорда . Якщо вхід безпечний, то проблем взагалі немає. Використовуйте правильний інструмент для правильної роботи, ось і все.


6

На всякий випадок, хто хто відвідує це питання у 2017 році чи пізніше і шукає простий у запам'ятовуванні спосіб, ось розроблена допис у блозі про доступ до вкладених об’єктів у JavaScript, не піддаючись бамбуку

Неможливо прочитати властивість "foo" невизначеного помилки

Доступ до вкладених об’єктів за допомогою зменшення масиву

Візьмемо цей приклад структури

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

Щоб мати доступ до вкладених масивів, ви можете написати власний утиліт зменшення масиву.

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.

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

З типом ваш код буде виглядати приблизно так

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

Відмова: Я є автором цього пакету.


6

КутовийJS

Підхід Шпейга дуже акуратний і чистий, хоча я знайшов цю відповідь під час пошуку рішення доступу до властивостей діапазону 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 () та remove ( ) операції.

Можна використовувати позначення крапок, дужки, індекси чисел, властивості чисел рядків та клавіші з неслововими символами. Просте використання нижче:

> 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

Якщо ви збираєтесь надсилати код як відповідь, поясніть, чому код відповідає на питання.
Тієсон Т.


2

Хоча зменшення добре, я здивований, що ніхто не використовував для кожного:

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

Тест


Ви навіть не перевіряєте, чи існує Obj [ключ] насправді. Це ненадійно.
Карлес Алколія

@CarlesAlcolea за замовчуванням js не перевірятиме, чи існує ключ об'єкта: a.b.cстворить виняток, якщо bу вашому об’єкті немає властивості . Якщо вам потрібно щось мовчки відхилити неправильний шлях (що я не рекомендую), ви все одно можете замінити forEach на цейkeys.forEach((key)=> obj = (obj||{})[key]);
Flavien Volken

Я пробігаю через нього предмет, на якому бракувало фігурної дужки, моє погано :)
Карлес Алколія

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

На основі відповіді Альнітака .

Я загорнув поліфіл в чек і звів функцію до єдиного ланцюгового скорочення.

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

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

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;

Я сподіваюся, що це питання стосується вашого питання ...

EDIT

Я не буду використовувати рядок для збереження свого роду 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);

Функції коротші, чіткіші, інтерпретатор перевіряє їх на наявність синтаксичних помилок тощо.

До речі, я вважаю, що простого завдання, зробленого в потрібний час, буде достатньо ...


Цікаво. Але в моєму випадку деякийObject ініціалізується ще тоді, коли я присвоюю значення part1name. Я знаю лише структуру. Ось чому я використовую рядок для опису структури. І сподіваюся, що зможу використовувати його для запиту моїх даних з якогось об'єкта. Дякуємо, що поділилися думкою. :)
Комаруло

@Komaruloh: Я думаю, ви б написали, що об’єкт ще НЕ ініціалізований, коли ви створюєте свої змінні. До речі, я не розумію, чому ви не можете виконати завдання у відповідний час?
Ейнекі

Вибачте, що не згадуєте, що деякийObject ще не ініціалізований. Що стосується причини, деякийObject отримує через веб-сервіс. І я хочу мати масив заголовка, який складається з part1name, part2qty тощо. Щоб я міг просто провести цикл через масив заголовків і отримати значення, яке я хотів, на основі значення part1name як "ключ" / шлях до someObject.
Комаруло

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

Побудова відповіді Альнітака:

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>

Щасти...

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