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

Accessing nested JavaScript objects and arrays by string path

提问人:Komaruloh 提问时间:6/27/2011 最后编辑:Lawrence CheroneKomaruloh 更新时间:9/1/2023 访问量:366299

问:

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

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 应填充 的值,即“Part 1”。与填充 60 的 part2quantity 相同。someObject.part1.name

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

JavaScript 路径 嵌套

评论

0赞 BonyT 6/27/2011
不确定你在这里问什么?您希望能够查询 part1.name 并返回文本“part1.name”?或者您想要一种方法来获取存储在 part1.name 中的值?
0赞 Rafay 6/27/2011
你有没有试过做这样的事情var part1name = someObject.part1name; `
1赞 Komaruloh 6/27/2011
@BonyT :我想查询 someObject.part1.name 并返回它的值(“第 1 部分”)。但是,我希望查询(我称之为“键”)存储在变量“part1name”中。感谢您的回复。@3nigma : 我当然有。但这不是我的本意。感谢您的回复。
1赞 Steve Black 3/20/2013
在重复的答案中,我喜欢 Fyr 的回答 stackoverflow.com/questions/8817394/......
1赞 Bergi 12/15/2015
另请参阅将点表示法中的 JavaScript 字符串转换为对象引用

答:

12赞 Hogan 6/27/2011 #1

我想你是在问这个:

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

评论

0赞 duri 6/27/2011
我认为 OP 要求最后一个解决方案。但是,字符串没有方法,而是 .Splitsplit
0赞 Komaruloh 6/27/2011
实际上,我问的是最后一个。partName 变量由字符串填充,指示要值的键结构。您的解决方案似乎很有意义。但是,我可能需要修改以扩展数据的深度,例如 4-5 级等。我想知道我是否可以用它统一地处理数组和对象?
1赞 Eineki 6/27/2011 #2

如果您需要在编码时访问不同的嵌套键而不知道它(解决它们将很简单),您可以使用数组表示法访问器:

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;

我希望这能解决你的问题......

编辑

我不会使用字符串来维护某种 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);

或者,如果您对 apply 方法感到不安

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

这些函数更短、更清晰,解释器会为您检查它们是否存在语法错误等。

顺便说一句,我觉得在正确的时间完成一个简单的任务就足够了......

评论

0赞 Komaruloh 6/27/2011
有趣。但在我的情况下,当我将值分配给 part1name 时,someObject 尚未初始化。我只知道结构。这就是为什么我使用字符串来描述结构的原因。并希望能够使用它从someObject查询我的数据。感谢您分享您的想法。:)
0赞 Eineki 6/27/2011
@Komaruloh :我想你会写,当你创建变量时,对象还没有初始化。顺便说一句,我不明白这一点,为什么你不能在适当的时间完成作业?
0赞 Komaruloh 6/27/2011
很抱歉没有提到 someObject 尚未初始化。至于原因,someObject 是通过 Web 服务获取的。我想要一个由 part1name、part2qty 等组成的标头数组。这样我就可以遍历标头数组并根据 part1name 值作为 someObject 的“键”/路径获取我想要的值。
67赞 Felix Kling 6/27/2011 #3

您必须自己解析字符串:

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

它使解析更容易。

演示

评论

0赞 Komaruloh 6/27/2011
@Felix Kling:你的解决方案确实为我提供了我需要的东西。我为此非常感谢你。但 Alnitak 也提供了不同的方法,并且似乎也有效。由于我只能选择一个答案,所以我会选择 Alnitak 答案。并不是说他的解决方案比你或类似的东西更好。无论如何,我真的很感谢你的解决方案和你付出的努力。
0赞 Alnitak 6/28/2011
@Felix FWIW - 从语法转换为属性语法非常简单。[]
0赞 hikaru 5/2/2014
我喜欢这个答案,因为我可以为我的用户提供更简单的路径格式 - 使用点表示法作为索引而不是括号。谢谢!
5赞 Snea 8/17/2014
如果将 while 循环更改为 ,则此代码也适用于没有点的字符串。while (l > 0 && (obj = obj[current]) && i < l)
1赞 CuddleBunny 6/2/2015
老实说,这是更好的答案,因为您实际上可以更改 obj[last] 的值,但如果您以另一种方式更改该值,则无法更改该值。
676赞 Alnitak 6/27/2011 #4

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

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

评论

41赞 t3dodson 1/14/2015
这效果很好。请将其包装为节点包,将其贡献给互联网。
18赞 Capaj 7/31/2015
@t3dodson我刚刚做到了:github.com/capaj/object-resolve-path 请注意,当您的属性名称本身包含“[]”时,这并不好。正则表达式会将其替换为“.”,并且它不能按预期工作
52赞 ian 8/13/2015
很棒的东西;使用 Lodash 库,还可以执行以下操作:_.get(object, nestedPropertyString);
21赞 ste2425 11/17/2015
这可能会迷失在评论的海洋中,但是如果您尝试解决不存在的属性,它就会出错。所以。添加检查以查看是否是对象可修复此问题。(你怎么去做取决于你)。查看更新的小提琴:jsfiddle.net/hEsys/418'part3[0].name.iDontExist'oif in
3赞 Alnitak 10/22/2019
@ThatGuyRob引入第三方库并不总是“更好”,无论如何,当我写这个答案时,这种方法甚至不存在。
31赞 5 revs, 2 users 91%Shanimal #5

使用 EVAL:

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

包装以在出错时返回未定义

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

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

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

评论

8赞 James Wilkins 8/22/2014
eval 是否是一个好主意取决于属性字符串数据的来源。我怀疑你有什么理由担心黑客通过静态的“var p='a.b.c';eval(p);“ 类型 call。这是一个非常好的主意。
43赞 TheZver 4/24/2013 #6

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

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

评论

11赞 Dominic 8/1/2013
谢谢,这是最好和最有效的答案 - jsfiddle.net/Jw8XB/1
0赞 TheZver 3/27/2016
@Endless,我想强调的是,路径应该用点分隔项目。牙套不起作用。即,要访问数组中的第一个项目,请使用“0.sp ace”。
1赞 abernier 10/26/2013 #7

最近遇到了同样的问题,并成功使用了嵌套对象/数组的 https://npmjs.org/package/tea-propertiesset

获取:

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

评论

0赞 Patrick Fisher 3/18/2014
“该模块已停产。使用 chaijs/pathval。
265赞 speigg 3/3/2014 #8

这是我使用的解决方案:

function resolve(path, obj=self, separator='.') {
    var properties = Array.isArray(path) ? path : path.split(separator)
    return properties.reduce((prev, curr) => 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赞 Alp 5/22/2014
使用 reduce 是一个很好的解决方案(也可以从 Underscore 或 Lodash 库中使用)_.reduce()
5赞 Platinum Azure 6/17/2014
我认为这里可能没有定义。你的意思是 ?selfthis
3赞 mroach 1/2/2017
以下是我对按路径设置值的补充:pastebin.com/jDp5sKT9
1赞 speigg 8/15/2018
@SC1000个好主意。这个答案是在大多数浏览器中默认参数可用之前编写的。我会将其更新为“function resolve(path, obj=self)”,因为引用全局对象作为默认值是有意的。
5赞 crush 4/13/2020
@AdamPlocher 我知道这已经很旧了,但我将其转换为打字稿,如下所示: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); }
2赞 caleb 6/14/2014 #9

现在有一个模块可以执行此操作:https://github.com/erictrinh/safe-accessnpm

用法示例:

var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
7赞 James Wilkins 8/22/2014 #10

在这里,我提供了更多方法,这些方法在许多方面似乎更快:

选项 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”应该不会有任何问题(尤其是在需要速度的情况下)。如果“通过网络”拉取属性路径(在线!?哈哈:P),那么是的,使用其他东西来确保安全。只有白痴才会说永远不要使用“eval”,因为什么时候使用它是有充分理由的。此外,“它用于 Doug Crockford 的 JSON 解析器。如果输入是安全的,那么完全没有问题。为正确的工作使用正确的工具,仅此而已。

7赞 nesinervink 2/26/2015 #11

AngularJS的

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

评论

0赞 georgeawg 3/6/2020
请参阅 AngularJS 具有 $scope.$eval 以了解使用 AngularJS 执行此操作的另一种方法。
27赞 Harish Ambady 4/11/2015 #12

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

function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };

在你的情况下,要从中获取值,只需执行:part1.namesomeObject

objectGet(someObject, 'part1.name');

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

评论

3赞 ArcangelZith 7/29/2016
函数 deep_value ( obj, path ) { return new Function( 'o', 'return o.' + path )( obj ); }
0赞 scipilot 9/24/2021
看起来很有前途,但它似乎在引擎盖下使用,如果您有 CSP 不安全评估保护,则被拒绝。evalRefused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script
3赞 Kyle 4/25/2015 #13

我还没有找到一个包来执行带有字符串路径的所有操作,所以我最终编写了自己的快速小包,它支持 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

298赞 Ian Walker-Sperber 7/9/2015 #14

现在,lodash 使用 .查看 https://lodash.com/docs#get_.get(obj, property)

文档中的示例:

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'

评论

12赞 Capaj 8/4/2015
这应该是唯一被接受的答案,因为这是唯一一个同时适用于点和括号语法的答案,当我们在路径中的键的字符串中有“[]”时,它不会失败。
12赞 Josh C. 8/22/2017
这。另外,它支持_.set(...)
0赞 DDave 11/1/2017
如果找不到对象会有什么好感?
22赞 Andre Figueiredo 2/20/2018
你开玩笑@Capaj?谁不想/不能使用 lodash?
5赞 run_the_race 3/29/2022
@卡帕伊...我不想将外部库用于几行代码。
4赞 Vitim.us 3/2/2016 #15
/**
 * 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")
15赞 James 12/9/2016 #16

这是一个带有 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"

甚至更好......

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

或 ES6 版本,带 reduce...

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

普伦克尔

3赞 Ben 12/26/2016 #17

简单的函数,允许字符串或数组路径。

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

评论

0赞 Tieson T. 12/26/2016
如果您要发布代码作为答案,请解释为什么代码可以回答这个问题。
239赞 Adriano Spadoni 5/8/2017 #18

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

评论

3赞 rob-gordon 6/29/2017
我喜欢这种技术。这真的很乱,但我想使用这种技术进行分配。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";
5赞 SmujMaiku 10/16/2017
我喜欢使用 reduce 的想法,但您的逻辑似乎不适合 和 值。 返回 而不是 。也许显式检查 null 或 undefined 将清除这些问题。0undefinednull{a:{b:{c:0}}}null0(p,c)=>p === undefined || p === null ? undefined : p[c]
1赞 Adriano Spadoni 10/16/2017
嗨,@SmujMaiku,“就绪”函数正确返回“0”、“undefined”和“null”,我刚刚在控制台上进行了测试:resolvePath({a:{b:{c:0}}},'a.b.c',null) => 0;它检查键是否存在,而不是值本身,从而避免了多次检查
1赞 ADJenks 9/25/2019
@AdrianoSpadoni 对不起,我觉得我指出的方式不是很有礼貌。祝你有美好的一天。
1赞 dsl101 7/27/2021
请注意,在某些情况下,带有 的版本仍将返回,例如 .要解决此问题,最后一行应该是defaultValueundefinedresolvePath({profile: {name: 'Bob'}}, 'profile.email', 'not set').reduce((o, p) => o?.[p] ?? defaultValue, object)
1赞 Vincent 11/1/2017 #19

基于前面的答案,我创建了一个也可以处理括号的函数。但由于分裂,它们内部没有点。

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);
}
2赞 Flavien Volken 12/15/2017 #20

虽然reduce很好,但我很惊讶没有人使用Each:

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

测试

评论

0赞 Neithan Max 7/22/2018
您甚至没有检查 obj[key] 是否真的存在。这是不可靠的。
0赞 Flavien Volken 7/23/2018
@CarlesAlcolea默认情况下,js 既不会检查对象的键是否存在,如果对象中没有属性,则会引发异常。如果你需要一些东西静默地关闭错误的键路径(我不建议这样做),你仍然可以用这个替换forEacha.b.cbkeys.forEach((key)=> obj = (obj||{})[key]);
0赞 Neithan Max 7/29/2018
我穿过它,一个缺少大括号的物体,我的坏:)
1赞 Oboo Cheng 2/23/2018 #21

// (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"]));

33赞 Nick Grealy 6/27/2018 #22

这可能永远不会见到曙光......但无论如何,它都在这里。

  1. 将括号语法替换为[].
  2. 按字符划分.
  3. 删除空白字符串
  4. 查找路径(否则undefined)

(若要查找对象的路径,请使用此 pathTo 解决方案。

// "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'
        }
        // ...
    ],
    'pa[rt3' : [
        {
            '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
console.log(deep_value(someObject, "part3[0].....name"));        // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name"));           // undefined - name does not support square brackets

评论

0赞 vitaly-t 12/25/2020
请注意,这将默默地吞噬大量无效路径,例如 .如果要进行智能处理,则不能只替换为 ,您必须删除每个 ,然后替换每个不在开头的 ,然后删除所有剩余的。...one...two...[].][.[
0赞 Nick Grealy 12/26/2020
@vitaly-t - 正确。请参阅 - 解决方案将空白路径视为无效。同样正确,字段名称中没有路径验证或支持方括号。如需更强大的解决方案,请考虑使用库,例如 npmjs.com/package/jsonpath-plusstedolan.github.io/jq#3 - remove blank strings
1赞 vitaly-t 12/26/2020
是的,现在有很多库可以进行详细的语法解析。不幸的是,它是有代价的,它比简单的索引方法慢很多倍,这对于 JavaScript 来说是很自然的,而且要快得多。一个简单的拆分就足够了。a.0.b.1
0赞 Nick Grealy 12/29/2020
绝对正确。这是一种权衡。如果你能更好地控制输入(例如控制方括号的使用),那么你就可以省去加载臃肿的库。
1赞 Nick Grealy 11/8/2021
@NickeManarin - 您使用的是 Typescript,而不是 Javascript。这个问题是无关的,但我相信它是抱怨,因为你没有分配类型。例如:(obj: any, path: string) => etc
9赞 Dinesh Pandiyan 7/8/2018 #23

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

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

使用 Array Reduce 访问嵌套对象

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

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

为了能够访问嵌套数组,您可以编写自己的数组 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.

还有一个出色的类型处理最小库类型,可以为您完成所有这些工作。

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

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

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

评论

1赞 nzn 6/14/2023
我想你的意思是而不是obj && obj[key] !== undefinedobj && obj[key] !== 'undefined'
2赞 Dm Mh 5/11/2019 #24

我正在用 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;
};
2赞 Ghominejad 1/6/2020 #25

您可以使用库。ramda

学习还可以帮助您轻松处理不可变对象。ramda


var obj = {
  a:{
    b: {
      c:[100,101,{
        d: 1000
      }]
    }
  }
};


var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);


https://codepen.io/ghominejad/pen/BayJZOQ

2赞 Mr. Polywhirl 4/8/2020 #26

根据 Alnitak 的回答

我把 polyfill 包装在一个检查中,并将函数简化为单个链式缩减。

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

5赞 vitaly-t 12/22/2020 #27

如果你想要一个可以正确检测和报告路径解析任何问题细节的解决方案,我为此编写了自己的解决方案 - 库路径值

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

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

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

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

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

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

resolveValue(someObject, path); //=> Part 3A
2赞 Alex Mckay 5/13/2021 #28

这可以通过将逻辑拆分为三个单独的函数来简化:

const isVal = a => a != null; // everything except undefined + null

const prop = prop => obj => {
    if (isVal(obj)) {
        const value = obj[prop];
        if (isVal(value)) return value;
        else return undefined;
    } else return undefined;
};

const path = paths => obj => {
    const pathList = typeof paths === 'string' ? paths.split('.') : paths;
    return pathList.reduce((value, key) => prop(key)(value), obj);
};

//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }

此变体支持:

  • 传递数组或字符串参数
  • 在调用和执行期间处理值undefined
  • 独立测试每个功能
  • 独立使用每个函数
10赞 Hashbrown 5/19/2021 #29

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

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

answer

如果您需要改用单个字符串,只需对其进行 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];
};

其他功能演示:
demonstration

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

评论

0赞 Nikk 10/5/2021
使用 获取以下错误。path is not iterableObject.get
3赞 huntzinger92 2/26/2022
就我而言,我收到 Object.get() 不是函数的错误。
2赞 Giulio 7/6/2022 #30

DotObject = obj => new Proxy(obj, {
  get: function(o,k) {
    const m = k.match(/(.+?)\.(.+)/)
    return m ? this.get(o[m[1]], m[2]) : o[k]
  }
})

const test = DotObject({a: {b: {c: 'wow'}}})
console.log(test['a.b.c'])