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

IT技术 javascript path nested
2020-12-30 23:22:27

我有一个这样的数据结构:

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 部分”。与填充 60 的 part2quantity 相同。

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

6个回答

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

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/查看工作演示

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

@ThatGuyRob 引入第三方库并不总是“更好”,无论如何,当我写这个答案时,该方法甚至不存在。
2021-02-20 23:22:27
这可能会在评论的海洋中迷失,但是如果您尝试解决不存在的属性,则会出错。所以'part3[0].name.iDontExist'添加检查以查看是否oif in修复问题的对象(你怎么做取决于你)。查看更新的小提琴:jsfiddle.net/hEsys/418
2021-02-25 23:22:27
这很好用。请通过将其包装为节点包来将其贡献给互联网。
2021-02-27 23:22:27
@t3dodson 我刚刚做了:github.com/capaj/object-resolve-path 请注意,当您的属性名称本身包含“[]”时,这不会很好。正则表达式将替换为 '.' 它没有按预期工作
2021-03-03 23:22:27
好东西; 使用 lodash 库,还可以执行以下操作:_.get(object, nestedPropertyString);
2021-03-04 23:22:27

现在 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'
这个。另外,它支持_.set(...)
2021-02-07 23:22:27
@Capaj 你在开玩笑吗?谁不想/不能使用 lodash?
2021-02-23 23:22:27
@DDave 如果作为对象传递的值未定义或不是对象,_.get将显示与在提供的对象中找不到键时相同的行为。例如_.get(null, "foo") -> undefined_.get(null, "foo", "bar") -> "bar"但是,此行为未在文档中定义,因此可能会发生变化。
2021-02-25 23:22:27
这应该是唯一被接受的答案,因为这是唯一一个同时适用于点和括号语法的答案,并且当我们在路径中的键字符串中有 '[]' 时它不会失败。
2021-03-02 23:22:27
如果找不到对象会怎样?
2021-03-05 23:22:27

这是我使用的解决方案:

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

限制:

  • 不能[]对数组索引使用方括号 ( ) — 尽管在分隔符标记之间指定数组索引(例如,.)可以正常工作,如上所示。
@SC1000 好主意。此答案是在大多数浏览器中提供默认参数之前编写的。我将其更新为“function resolve(path, obj=self)”,因为将全局对象作为默认引用是有意的。
2021-02-06 23:22:27
使用 reduce 是一个很好的解决方案(也可以_.reduce()从 underscore 或 lodash 库中使用)
2021-02-11 23:22:27
这是我按路径设置值的补充:pastebin.com/jDp5sKT9
2021-02-22 23:22:27
@AdamPlocher 我知道这现在已经过时了,但我将其转换为typescript,如下所示: export function resolvePath(path: string | string[], obj: any, separator = '.') { const properties = Array.isArray(path) ? path : path.split(separator); return properties.reduce((prev, curr) => prev && prev[curr], obj); }
2021-03-02 23:22:27
我认为self这里可能是未定义的。你的意思是this
2021-03-03 23:22:27

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

'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], {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
嗨@SmujMaiku,“准备使用”函数为“0”、“未定义”和“空”正确返回,我刚刚在控制台上进行了测试:resolvePath({a:{b:{c:0}}},' abc',null) => 0; 它检查键是否存在而不是值本身,从而避免多次检查
2021-02-17 23:22:27
我喜欢使用 reduce 的想法,但您的逻辑似乎不适用于0,undefinednull值。 {a:{b:{c:0}}}返回null而不是0. 也许明确检查 null 或 undefined 会解决这些问题。 (p,c)=>p === undefined || p === null ? undefined : p[c]
2021-02-20 23:22:27
这里 defaultValue 不起作用,虽然使用Reflect.has(o, k) ? ...(ES6 Reflect.has ) 起作用
2021-02-27 23:22:27
我喜欢这种技术。这真的很乱,但我想使用这种技术进行分配。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";
2021-03-05 23:22:27
请注意,在某些情况下,带有的版本defaultValue仍然会返回undefined- 例如resolvePath({profile: {name: 'Bob'}}, 'profile.email', 'not set'). 要解决这个问题,最后一行应该是.reduce((o, p) => o?.[p] ?? defaultValue, object)
2021-03-06 23:22:27

您必须自己解析字符串:

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

它使解析更容易。

演示

@Felix Kling:您的解决方案确实为我提供了我需要的东西。我非常感谢你。但是 Alnitak 也提供了不同的方法,并且似乎也可以工作。由于我只能选择一个答案,所以我会选择 Alnitak 的答案。并不是说他的解决方案比你或类似的东西更好。无论如何,我非常感谢您提供的解决方案和努力。
2021-02-14 23:22:27
老实说,这是更好的答案,因为您实际上可以更改 obj[last] 的值,但是如果您以其他方式进行更改,则无法更改该值。
2021-02-15 23:22:27
@Felix FWIW - 从[]语法转换为属性语法非常简单。
2021-02-17 23:22:27
我喜欢这个答案,因为我可以为我的用户提供更简单的路径格式 - 对索引使用点表示法而不是括号。谢谢!
2021-02-18 23:22:27
如果将 while 循环更改为,while (l > 0 && (obj = obj[current]) && i < l)则此代码也适用于没有点的字符串。
2021-03-02 23:22:27