Доступ к вложенным объектам JavaScript с помощью строкового ключа
у меня есть структура данных, как это :
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 's значение, которое является "Часть 1". То же самое с part2quantity, который заполнен 60.
есть ли в любом случае, чтобы достичь этого с помощью чистого javascript или JQuery?
28 ответов:
Я только что сделал это на основе некоторого аналогичного кода, который у меня уже был, похоже, работает:
Object.byString = function(o, s) { s = s.replace(/\[(\w+)\]/g, '.'); // 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- за неправильного индекса.
это решение я использую:
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ограничения:
- нельзя использовать скобки (
[]) для индексов массива - хотя указание индексов массива между токеном разделителя (например,.) работает отлично, как показано выше.
теперь это поддерживается 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'
вам придется разобрать строку самостоятельно:
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";Это облегчает разбор.
работает для массивов / массивов внутри объекта. Защита от недопустимых значений.
/** * 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>
ES6: только одна строка в Vanila JS (она возвращает null, если не находит вместо ошибки):
'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)или exemple:
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})для готовой к использованию функции, которая также распознает false, 0 и отрицательное число и принимает значения по умолчанию в качестве параметра:
const resolvePath = (object, path, defaultValue) => path .split('.') .reduce((o, p) => o ? o[p] : defaultValue, object)пример использования:
resolvePath(window,'document.body') => <body> resolvePath(window,'document.body.xyz') => undefined resolvePath(window,'document.body.xyz', null) => null resolvePath(window,'document.body.xyz', 1) => 1бонус:
до set путь (запрошенный @rob-gordon) вы можете использование:
const setPath = (object, path, value) => path .split('.') .reduce((o,p) => o[p] = path.split('.').pop() === p ? 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)exemple
const myVar = {a:{b:[{c:1}]}} resolvePath(myVar,'a.b[0].c') => 1 resolvePath(myVar,'a["b"][\'0\'].c') => 1
использование eval:
var part1name = eval("someObject.part1.name");обернуть возвращает значение undefined в случае ошибки
function path(obj, path) { try { return eval("obj." + path); } catch(e) { return undefined; } }http://jsfiddle.net/shanimal/b3xTw/
пожалуйста, используйте здравый смысл и осторожность при использовании силы eval. Это немного похоже на световой меч, если вы включите его, есть 90% шанс, что вы будете разорвать конечность. Это не для всех.
вы можете получить значение элемента deep object с точечной нотацией без какой-либо внешней библиотеки JavaScript с помощью простого следующего трюка:
new Function('_', 'return _.' + path)(obj);в вашем случае, чтобы получить значение
part1.nameСsomeObjectтак же:new Function('_', 'return _.part1.name')(someObject);вот простая скрипка демо:https://jsfiddle.net/harishanchu/oq5esowf/
здесь я предлагаю больше способов, которые кажутся более быстрыми во многих отношениях:
Вариант 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 Дуга Крокфорда."Если вход безопасен, то никаких проблем вообще нет. Используйте правильный инструмент для правильной работы, вот и все.
Я думаю, что вы просите за это:
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]];
подход Speigg очень аккуратный и чистый, хотя я нашел этот ответ при поиске решения доступа к свойствам AngularJS $scope по строковому пути и с небольшой модификацией он выполняет эту работу:
$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')
Это один лайнер с лодашь.
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);
Я еще не нашел пакет для выполнения всех операций со строковым путем, поэтому я написал свой собственный быстрый маленький пакет, который поддерживает операции 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'
простая функция, позволяющая использовать либо строку, либо путь к массиву.
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
есть
npmмодуль теперь для этого:https://github.com/erictrinh/safe-accessпример использования:
var access = require('safe-access'); access(very, 'nested.property.and.array[0]');
на всякий случай, кто посещает этот вопрос в 2017 году или позже и ищем легко запомнить кстати, вот подробный пост в блоге на доступ к вложенным объектам в JavaScript Не будучи обмануты
не удается прочитать свойство ' foo ' из undefined
Доступ К Вложенным Объектам С Помощью Массива Reduce
давайте возьмем этот пример структуры
const user = { id: 101, email: '[email protected]', 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.существует также отличный тип обработки минимальной библиотеки typy что делает все это для вас.
С typy, ваш код будет выглядеть так
const city = t(user, 'personalInfo.address[0].city').safeObject;отказ от ответственности: я автор этого пакета.
это, вероятно, никогда не увидит свет дня... но все равно это здесь.
- заменить
[]синтаксис кронштейн с.- разделить на
'символ- удалить пустые строки
- найти путь (в противном случае
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
/** * 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")
в то время как сокращение хорошо, я удивлен, что никто не использовал forEach:
function valueForKeyPath(obj, path){ const keys = path.split('.'); keys.forEach((key)=> obj = obj[key]); return obj; };
Если вам нужно получить доступ к другому вложенному ключу, не зная его во время кодирования (будет тривиально обращаться к ним) , вы можете использовать метод доступа к нотации массива:
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);функции короче, понятнее, интерпретатор проверяет их для вас на наличие синтаксических ошибок и так далее.
кстати, я чувствую, что простое задание, сделанное в нужное время, будет достаточно...
только что был тот же вопрос недавно и успешно используется https://npmjs.org/package/tea-properties который также
setвложенные объекты / массивы:get:
var o = { prop: { arr: [ {foo: 'bar'} ] } }; var properties = require('tea-properties'); var value = properties.get(o, 'prop.arr[0].foo'); assert(value, 'bar'); // trueset:
var o = {}; var properties = require('tea-properties'); properties.set(o, 'prop.arr[0].foo', 'bar'); assert(o.prop.arr[0].foo, 'bar'); // true
решения здесь предназначены только для доступа к глубоко вложенным ключам. Мне нужен был один для доступа, добавления, изменения и удаления ключей. Вот что я придумал:
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 для удаления.
вместо строки можно использовать массив, адресующий вложенные объекты и массивы, например:
["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);
основываясь на предыдущем ответе, я создал функцию, которая также может обрабатывать скобки. Но никаких точек внутри них из-за раскола нет.
function get(obj, str) { return str.split(/\.|\[/g).map(function(crumb) { return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!)[^\]|\.)*?)$/, (match, quote, str) => str.replace(/\(\)?/g, "")); }).reduce(function(obj, prop) { return obj ? obj[prop] : undefined; }, obj); }
// (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,".") .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"]));
работает с
Underscore' spropertyили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>Удачи...
Как насчет такого решения:
setJsonValue: function (json, field, val) { if (field !== undefined){ try { eval("json." + field + " = val"); } catch(e){ ; } } }и этот, для получения:
getJsonValue: function (json, field){ var value = undefined; if (field !== undefined) { try { eval("value = json." + field); } catch(e){ ; } } return value; };вероятно, некоторые сочтут их небезопасными, но они должны быть намного быстрее, чем разбор строки.
отталкиваясь от ответа Альнитака:
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, '.'); // 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 package и github С
Comments