通过字符串路径访问嵌套的 JavaScript 对象和数组

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6491463/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 22:00:52  来源:igfitidea点击:

Accessing nested JavaScript objects and arays by string path

javascriptjquerypathnested

提问by Komaruloh

I have a data structure like this :

我有这样的数据结构:

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

And I would like to access the data using these variable :

我想使用这些变量访问数据:

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

part1name should be filled with someObject.part1.name's value, which is "Part 1". Same thing with part2quantity which filled with 60.

part1name 应填写someObject.part1.name的值,即“第 1 部分”。与填充 60 的 part2quantity 相同。

Is there anyway to achieve this with either pure javascript or JQuery?

有没有办法用纯 javascript 或 JQuery 来实现这一点?

回答by Alnitak

I just made this based on some similar code I already had, it appears to work:

我只是根据我已经拥有的一些类似代码制作了这个,它似乎有效:

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

Usage::

用法::

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

See a working demo at http://jsfiddle.net/alnitak/hEsys/

http://jsfiddle.net/alnitak/hEsys/查看工作演示

EDITsome have noticed that this code will throw an error if passed a string where the left-most indexes don't correspond to a correctly nested entry within the object. This is a valid concern, but IMHO best addressed with a try / catchblock when calling, rather than having this function silently return undefinedfor an invalid index.

编辑有些人已经注意到,如果传递一个字符串,其中最左边的索引不对应于对象中正确嵌套的条目,则此代码将引发错误。这是一个有效的问题,但恕我直言,最好try / catch在调用时使用块解决,而不是让此函数静默返回undefined无效索引。

回答by speigg

This is the solution I use:

这是我使用的解决方案:

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

Example usage:

用法示例:

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

Limitations:

限制:

  • Can't use brackets ([]) for array indices—though specifying array indices between the separator token (e.g., .) works fine as shown above.
  • 不能[]对数组索引使用方括号 ( ) — 尽管在分隔符标记之间指定数组索引(例如,.)可以正常工作,如上所示。

回答by Ian Walker-Sperber

This is now supported by lodash using _.get(obj, property). See https://lodash.com/docs#get

现在 lodash 使用_.get(obj, property). 见https://lodash.com/docs#get

Example from the docs:

文档中的示例:

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'

回答by Adriano Spadoni

ES6: Only one line in Vanila JS (it return null if don't find instead of giving error):

ES6:Vanila JS 中只有一行(如果没有找到则返回 null 而不是给出错误):

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

Or example:

或者例如:

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

For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:

对于一个也可以识别假、0 和负数并接受默认值作为参数的即用型函数:

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

Example to use:

使用示例:

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

Bonus:

奖金

To seta path (Requested by @rob-gordon) you can use:

设置路径(由@rob-gordon 请求),您可以使用:

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

Example:

例子:

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

Access array with []:

使用 [] 访问数组

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

Example:

例子:

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

回答by Felix Kling

You'd have to parse the string yourself:

您必须自己解析字符串:

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

This required that you also define array indexes with dot notation:

这要求您还使用点表示法定义数组索引:

var part3name1 = "part3.0.name";

It makes the parsing easier.

它使解析更容易。

DEMO

演示

回答by TheZver

Works for arrays / arrays inside the object also. Defensive against invalid values.

也适用于对象内的数组/数组。防御无效值。

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

回答by Shanimal

using eval:

使用评估:

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

wrap to return undefined on error

换行以在出错时返回 undefined

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

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

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

Please use common sense and caution when wielding the power of eval. It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb. Its not for everybody.

在运用 eval 的力量时,请使用常识和谨慎。它有点像一把光剑,如果你打开它,你有 90% 的机会会切断一条肢体。它不适合所有人。

回答by Harish Anchu

You can manage to obtain value of a deep object member with dot notation without any external JavaScript library with the simple following trick:

您可以通过以下简单的技巧,在没有任何外部 JavaScript 库的情况下,使用点表示法获取深层对象成员的值:

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

In your case to obtain value of part1.namefrom someObjectjust do:

在您的情况下,要获取part1.namefrom 的值,someObject请执行以下操作:

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

Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/

这是一个简单的小提琴演示:https: //jsfiddle.net/harishanchu/oq5esowf/

回答by Nick Grealy

This will probably never see the light of day... but here it is anyway.

这可能永远不会见天日......但无论如何它在这里。

  1. Replace []bracket syntax with .
  2. Split on .character
  3. Remove blank strings
  4. Find the path (otherwise undefined)
  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

回答by James

It's a one liner with lodash.

这是一个带有 lodash 的单衬里。

const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"

Or even better...

或者更好...

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

Or ES6 version w/ reduce...

或 ES6 版本带减少...

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

Plunkr

普朗克