使用字符串键访问嵌套的JavaScript对象

Kom*_*loh 410 javascript jquery nested path

我有这样的数据结构:

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'
        }
    ]
};
Run Code Online (Sandbox Code Playgroud)

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

var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
Run Code Online (Sandbox Code Playgroud)

part1name应该用someObject.part1.name's值填充,即"Part 1".part2quantity与60相同.

无论如何使用纯javascript或JQuery实现这一点?

Aln*_*tak 497

我刚刚根据我已经拥有的一些类似代码制作了它,它似乎工作:

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;
}
Run Code Online (Sandbox Code Playgroud)

用法::

Object.byString(someObj, 'part3[0].name');
Run Code Online (Sandbox Code Playgroud)

请参阅http://jsfiddle.net/alnitak/hEsys/上的工作演示

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

  • 这很好用.请将它作为节点包包装到互联网上. (16认同)
  • 好东西; 使用lodash库,人们也可以这样做:``_``(object,nestedPropertyString);``` (16认同)
  • 这可能会在评论的海洋中迷失,但如果您尝试解决不存在的属性,则会出错.所以`'part3 [0] .name.iDontExist'`.添加一个检查以查看`o`是否是`if in`中的对象可以解决问题.(你如何做到这一点取决于你).查看更新的小提琴:http://jsfiddle.net/hEsys/418/ (15认同)
  • @ t3dodson我刚刚做了:https://github.com/capaj/object-resolve-path只是要注意,当你的属性名称本身包含'[]'时,这不会很好.正则表达式将替换为'.' 它没有按预期工作 (11认同)
  • 这太金了.我们有一个基于配置的应用程序,这有点帮助!谢谢! (2认同)
  • @ThatGuyRob 引入第三方库并不总是“更好”,无论如何,当我写这个答案时,该方法甚至不存在。 (2认同)

spe*_*igg 170

这是我使用的解决方案:

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

用法示例:

// 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
Run Code Online (Sandbox Code Playgroud)

限制:

  • 不能[]对数组索引使用bracket() - 尽管在分隔符令牌之间指定数组索引(例如),.如上所示正常工作.

  • 使用reduce是一个很好的解决方案(也可以使用下划线或lodash库中的`_.reduce()`) (7认同)
  • @PlatinumAzure https://developer.mozilla.org/en-US/docs/Web/API/Window.self (7认同)
  • 不,"自我"是定义的.`this`会不正确. (3认同)
  • 我认为`self`可能在这里未定义.你的意思是"这个"? (2认同)
  • 这是我通过路径设置值的补充:http://pastebin.com/jDp5sKT9 (2认同)
  • @AdamPlocher我知道现在已经很旧了,但我将其转换为打字稿,如下所示: `export function resolvePath(path: string | string[], obj: any, seperator = '.') { constproperties = Array.isArray(path )?路径:路径.split(分隔符);return properties.reduce((prev, curr) =&gt; prev &amp;&amp; prev[curr], obj); }` (2认同)

Ian*_*ber 158

这现在由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'
Run Code Online (Sandbox Code Playgroud)

  • 这应该是唯一可以接受的答案,因为这是唯一一个同时用于点和括号语法的答案,当我们在路径中的键的字符串中有'[]'时,它不会失败. (6认同)
  • 这个.另外,它支持`_.set(...)` (6认同)
  • @ Capaj ...我不想使用外部库来编写几行代码。 (5认同)
  • @Capaj你在开玩笑吗?谁不想/不能使用lodash? (3认同)

Fel*_*ing 61

你必须自己解析字符串:

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';
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var part3name1 = "part3.0.name";
Run Code Online (Sandbox Code Playgroud)

它使解析更容易.

DEMO

  • 如果你将while循环改为`while(l> 0 &&(obj = obj [current])&& i <l)`那么这段代码也适用于没有点的字符串. (4认同)

Adr*_*oni 51

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

'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
Run Code Online (Sandbox Code Playgroud)

或例如:

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

对于即用型函数,它还可以识别false,0和负数,并接受默认值作为参数:

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

例如:

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
Run Code Online (Sandbox Code Playgroud)

奖金:

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

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

例:

let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}
Run Code Online (Sandbox Code Playgroud)

使用[]访问数组:

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

为例

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

  • 我喜欢这种技术。这真的很乱,但我想使用这种技术进行分配。`让o = {a:{b:{c:1}}}; 让 str = 'abc'; str.split('.').splice(0, str.split('.').length - 1).reduce((p,c)=&gt;p&amp;&amp;p[c]||null, o)[str.split ('.').slice(-1)] = "一些新值";` (2认同)
  • 我喜欢使用 reduce 的想法,但对于 `0`、`undefined` 和 `null` 值,你的逻辑似乎不合适。`{a:{b:{c:0}}}` 返回 `null` 而不是 `0`。也许明确检查 null 或 undefined 会解决这些问题。`(p,c)=&gt;p === 未定义 || p === 空?未定义:p[c]` (2认同)

The*_*ver 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'])`);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>
Run Code Online (Sandbox Code Playgroud)

  • 谢谢这是最好,最高效的答案 - http://jsfiddle.net/Jw8XB/1/ (9认同)

Sha*_*mal 23

使用eval:

var part1name = eval("someObject.part1.name");
Run Code Online (Sandbox Code Playgroud)

wrap以在错误时返回undefined

function path(obj, path) {
    try {
        return eval("obj." + path);
    } catch(e) {
        return undefined;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

挥动eval的力量时请使用常识和谨慎.它有点像轻剑,如果你打开它有90%的几率你会切断一个肢体.它不适合所有人.

  • eval是否是个好主意取决于属性字符串数据的来源.我怀疑你有任何理由担心黑客通过静态"var p ='abc'; eval(p);" 打电话.这是一个非常好的主意. (6认同)

Har*_*chu 17

您可以设置使用点表示法获取深层对象成员的值,而不使用任何外部JavaScript库,只需使用以下简单技巧:

new Function('_', 'return _.' + path)(obj);
Run Code Online (Sandbox Code Playgroud)

在你的情况下获得的价值part1.namesomeObject刚做:

new Function('_', 'return _.part1.name')(someObject);
Run Code Online (Sandbox Code Playgroud)

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

  • function deep_value(obj,path){return new Function('o','return o.'+ path)(obj); } (3认同)

Nic*_*aly 11

这可能永远不会看到一天的光......但无论如何它在这里.

  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
Run Code Online (Sandbox Code Playgroud)


Has*_*own 10

与其尝试模拟 JS 语法,你将不得不花费大量的计算解析,或者只是犯错/忘记诸如一堆这样的答案(带有.s 的键,有人吗?),只需使用一个键数组。

var part1name     = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1    = Object.get(someObject, ['part3', 0, 'name']);
Run Code Online (Sandbox Code Playgroud)

回答

如果您需要使用单个字符串,只需将其 JSONify 即可。
此方法的另一个改进是您可以删除/设置根级别对象。

function resolve(obj, path) {
    let root = obj = [obj];
    path = [0, ...path];
    while (path.length > 1)
        obj = obj[path.shift()];
    return [obj, path[0], root];
}
Object.get = (obj, path) => {
    let [parent, key] = resolve(obj, path);
    return parent[key];
};
Object.del = (obj, path) => {
    let [parent, key, root] = resolve(obj, path);
    delete parent[key];
    return root[0];
};
Object.set = (obj, path, value) => {
    let [parent, key, root] = resolve(obj, path);
    parent[key] = value;
    return root[0];
};
Run Code Online (Sandbox Code Playgroud)

其他功能演示:
示范

for /不是必需的,除非您的路径可能为空(操作根对象)bob = 。我通过使用保留对原始对象的引用并先进行检查来 证明我不会克隆该对象.set(.del(
stevebob == steve //true.set(

  • 就我而言,我得到了 Object.get() 不是函数的错误。 (3认同)

Jam*_*mes 9

这是一个带有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"
Run Code Online (Sandbox Code Playgroud)

甚至更好......

const val = _.get(deep, prop);
Run Code Online (Sandbox Code Playgroud)

或ES6版本w/reduce ...

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

Plunkr


Hog*_*gan 7

我想你要求的是:

var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 =  someObject.part3[0].name;
Run Code Online (Sandbox Code Playgroud)

你可能要求这个:

var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 =  someObject["part3"][0]["name"];
Run Code Online (Sandbox Code Playgroud)

两者都有效


或者也许你是要求这个

var partName = "part1";
var nameStr = "name";

var part1name = someObject[partName][nameStr];
Run Code Online (Sandbox Code Playgroud)

最后你可能会要求这个

var partName = "part1.name";

var partBits = partName.split(".");

var part1name = someObject[partBits[0]][partBits[1]];
Run Code Online (Sandbox Code Playgroud)


Jam*_*ins 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;
}
Run Code Online (Sandbox Code Playgroud)

选项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)
Run Code Online (Sandbox Code Playgroud)

方案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;
}
Run Code Online (Sandbox Code Playgroud)

JSPerf:http://jsperf.com/ways-to-dereference-a-delimited-property-string/3

"eval(...)"仍然是王者(表现明智).如果你有直接在你控制下的属性路径,使用'eval'应该没有任何问题(特别是如果需要速度).如果"通过电线"拉动属性路径(在线上!?lol:P),那么是的,使用别的东西是安全的.只有白痴会说根本不会使用"eval",因为有很好的理由何时使用它.此外,"它用于Doug Crockford的JSON解析器." 如果输入是安全的,那么根本没有问题.使用正确的工具来做正确的工作,就是这样.


Din*_*yan 7

以防万一,任何人在 2017 年或以后访问这个问题并寻找一种易于记忆的方式,这里有一篇关于在 JavaScript 中访问嵌套对象而不会被迷惑的详尽博客文章

无法读取未定义错误的属性“foo”

使用 Array Reduce 访问嵌套对象

让我们以这个示例结构为例

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

为了能够访问嵌套数组,您可以编写自己的数组 reduce util。

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.
Run Code Online (Sandbox Code Playgroud)

还有一个很好的处理类型库最小typy,做这一切为您服务。

使用typy,您的代码将如下所示

const city = t(user, 'personalInfo.address[0].city').safeObject;
Run Code Online (Sandbox Code Playgroud)

免责声明:我是这个包的作者。


nes*_*ink 6

Speigg的方法非常整洁干净,虽然我在搜索通过字符串路径访问AngularJS $ scope属性的解决方案时发现了这个回复,并且稍作修改就完成了这项工作:

$scope.resolve = function( path, obj ) {
    return path.split('.').reduce( function( prev, curr ) {
        return prev[curr];
    }, obj || this );
}
Run Code Online (Sandbox Code Playgroud)

只需将此函数放在根控制器中,并将其用于任何子范围,如下所示:

$scope.resolve( 'path.to.any.object.in.scope')
Run Code Online (Sandbox Code Playgroud)


vit*_*y-t 5

如果您想要一个能够正确检测和报告路径解析任何问题详细信息的解决方案,我为此编写了自己的解决方案 - 库path-value

const {resolveValue} = require('path-value');

resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A
Run Code Online (Sandbox Code Playgroud)

请注意,对于索引,我们使用.0, 而不是[0],因为解析后者会增加性能损失,而.0直接在 JavaScript 中工作,因此速度非常快。

但是,也支持完整的 ES5 JavaScript 语法,只需首先对其进行标记:

const {resolveValue, tokenizePath} = require('path-value');

const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']

resolveValue(someObject, path); //=> Part 3A
Run Code Online (Sandbox Code Playgroud)