简体   繁体   English

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

[英]Accessing nested JavaScript objects and arrays by string path

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". part1name 应填写someObject.part1.name的值,即“Part 1”。 Same thing with part2quantity which filled with 60.与填充 60 的 part2quantity 相同。

Is there anyway to achieve this with either pure javascript or JQuery?无论如何用纯 javascript 或 JQuery 来实现这一点?

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, '.$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;
}

Usage::用法::

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

See a working demo at http://jsfiddle.net/alnitak/hEsys/请参阅http://jsfiddle.net/alnitak/hEsys/上的工作演示

EDIT some 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 / catch block when calling, rather than having this function silently return undefined for an invalid index.这是一个有效的问题,但恕我直言,最好在调用时使用try / catch块来解决,而不是让此函数静默返回undefined无效索引。

This is now supported by lodash using _.get(obj, property) .现在 lodash 使用_.get(obj, property)支持这一点。 See https://lodash.com/docs#get请参阅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'

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 (eg, . ) works fine as shown above.不能对数组索引使用方括号 ( [] ) — 尽管在分隔符标记之间指定数组索引(例如. )可以正常工作,如上所示。

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

With Optional chaining operator :使用可选链接运算符

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

For a ready to use function that also recognizes false, 0 and negative number and accept default values as parameter:对于一个即用型函数,它也可以识别 false、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 set a 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

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演示

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>

using eval:使用评估:

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

wrap to return undefined on error换行以在错误时返回未定义

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.在使用 eval 的力量时,请使用常识并谨慎行事。 It's a bit like a light saber, if you turn it on there's a 90% chance you'll sever a limb.这有点像一把光剑,如果你打开它,你有 90% 的机会会断肢。 Its not for everybody.它不适合所有人。

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 )找到路径(否则undefined

(For finding a path to an object, use this pathTo solution.) (要查找对象的路径,请使用此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

You can manage to obtain the value of a deep object member with dot notation without any external JavaScript library with the following simple trick:您可以通过以下简单技巧设法使用点表示法获取深层对象成员的值,而无需任何外部 JavaScript 库:

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

In your case to obtain value of part1.name from someObject just do:在您的情况下,要从someObject获取part1.name的值,只需执行以下操作:

objectGet(someObject, 'part1.name');

Here is a simple fiddle demo: https://jsfiddle.net/harishanchu/oq5esowf/这是一个简单的小提琴演示: https ://jsfiddle.net/harishanchu/oq5esowf/

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 Plunkr

I think you are asking for this:我想你在问这个:

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

You could be asking for this:你可能会问这个:

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

Both of which will work两者都将起作用


Or maybe you are asking for this或者也许你要这个

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

var part1name = someObject[partName][nameStr];

Finally you could be asking for this最后你可能会要求这个

var partName = "part1.name";

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

var part1name = someObject[partBits[0]][partBits[1]];

Just in case, anyone's visiting this question in 2017 or later and looking for an easy-to-remember way, here's an elaborate blog post on Accessing Nested Objects in JavaScript without being bamboozled by以防万一,任何人在 2017 年或以后访问这个问题并寻找一种易于记忆的方式,这里有一篇关于在 JavaScript 中访问嵌套对象而不被迷惑的详细博客文章

Cannot read property 'foo' of undefined error无法读取未定义错误的属性“foo”

Access Nested Objects Using Array Reduce使用数组归约访问嵌套对象

Let's take this example structure让我们以这个示例结构为例

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

To be able to access nested arrays, you can write your own array reduce util.为了能够访问嵌套数组,您可以编写自己的数组 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.

There is also an excellent type handling minimal library typy that does all this for you.还有一个出色的类型处理最小库类型,可以为您完成所有这些工作。

With typy, your code will look like this使用 typy,您的代码将如下所示

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

Disclaimer: I am the author of this package.免责声明:我是这个包的作者。

Instead of trying to emulate JS syntax which you will have to spend a bunch of compute parsing, or just get wrong/forget things like a bunch of these answers (keys with . s in, anyone?), just use an array of keys.与其尝试模拟 JS 语法,否则您将不得不花费大量计算解析,或者只是弄错/忘记一堆这些答案(带有. s 的键,有人吗?),只需使用一组键。

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

回答

If you need to use a single string instead, simply JSONify it.如果您需要使用单个字符串,只需 JSONify 即可。
Another improvement in this method is that you can delete/set the root level object.此方法的另一个改进是您可以删除/设置根级别对象。

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

Demo of other features:其他功能演示:
示范

The bob = for .set( / .del( isn't necessary unless your path might be empty (manipulating the root object).除非您的路径可能为空(操作根对象),否则bob = for .set( / .del(不是必需的。
I prove that I don't clone the object by using steve to keep a reference to the original and checking bob == steve //true after that first .set(我证明我没有通过使用steve来保持对原始对象的引用并在第一个bob == steve //true .set(

Here I offer more ways, which seem faster in many respects:在这里,我提供了更多方法,在许多方面看起来更快:

Option 1: Split string on .选项 1:在 . or [ or ] or ' or ", reverse it, skip empty items.或 [ 或 ] 或 ' 或 ",反转它,跳过空项目。

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

Option 2 (fastest of all, except eval ): Low level character scan (no regex/split/etc, just a quick char scan).选项 2(最快,除了eval ):低级字符扫描(没有正则表达式/拆分/等,只是一个快速的字符扫描)。 Note: This one does not support quotes for indexes.注意:这个不支持索引的引号。

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)

Option 3: ( new : option 2 expanded to support quotes - a bit slower, but still fast)选项 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 JSPerf:http: //jsperf.com/ways-to-dereference-a-delimited-property-string/3

"eval(...)" is still king though (performance wise that is). “eval(...)” 仍然是王者(即性能方面)。 If you have property paths directly under your control, there shouldn't be any issues with using 'eval' (especially if speed is desired).如果您直接控制了属性路径,则使用“eval”应该没有任何问题(尤其是在需要速度的情况下)。 If pulling property paths "over the wire" ( on the line !? lol :P), then yes, use something else to be safe.如果将属性路径“拉过电线”(在线!?大声笑:P),那么是的,使用其他东西是安全的。 Only an idiot would say to never use "eval" at all, as there ARE good reasons when to use it.只有白痴才会说永远不要使用“eval”,因为有充分的理由何时使用它。 Also, "It is used in Doug Crockford's JSON parser ."此外,“它用于Doug Crockford 的 JSON 解析器。” If the input is safe, then no problems at all.如果输入是安全的,那么就没有问题了。 Use the right tool for the right job, that's it.为正确的工作使用正确的工具,就是这样。

AngularJS AngularJS

Speigg's approach is very neat and clean, though I found this reply while searching for the solution of accessing AngularJS $scope properties by string path and with a little modification it does the job: Speigg 的方法非常简洁明了,尽管我在搜索通过字符串路径访问 AngularJS $scope 属性的解决方案时发现了这个回复,并且稍加修改就可以完成这项工作:

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

Just place this function in your root controller and use it any child scope like this:只需将此函数放在您的根控制器中,然后在任何子范围内使用它,如下所示:

$scope.resolve( 'path.to.any.object.in.scope')
/**
 * 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;
}

Works with适用于

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

If you want a solution that can properly detect and report details of any issue with the path parsing, I wrote my own solution to this - library path-value .如果您想要一个可以正确检测和报告路径解析任何问题的详细信息的解决方案,我为此编写了自己的解决方案 - library 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

Note that for indexes we use .0 , and not [0] , because parsing the latter adds a performance penalty, while .0 works directly in JavaScript, and is thus very fast.请注意,对于索引,我们使用.0而不是[0] ,因为解析后者会增加性能损失,而.0直接在 JavaScript 中工作,因此非常快。

However, full ES5 JavaScript syntax is also supported, it just needs to be tokenized first:然而,也支持完整的 ES5 JavaScript 语法,它只需要首先被标记化:

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

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

resolveValue(someObject, path); //=> Part 3A

I haven't yet found a package to do all of the operations with a string path, so I ended up writing my own quick little package which supports insert(), get() (with default return), set() and remove() operations.我还没有找到一个包来使用字符串路径执行所有操作,所以我最终编写了自己的快速小包,它支持 insert()、get()(默认返回)、set() 和 remove( ) 操作。

You can use dot notation, brackets, number indices, string number properties, and keys with non-word characters.您可以使用点表示法、方括号、数字索引、字符串数字属性和非单词字符的键。 Simple usage below:下面的简单用法:

> 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://www.npmjs.com/package/jsocrud

https://github.com/vertical-knowledge/jsocrud https://github.com/vertical-knowledge/jsocrud

Simple function, allowing for either a string or array path.简单的函数,允许字符串或数组路径。

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

OR或者

console.log(get(obj, ['a', 'b', 'c'])); //foo

There is an npm module now for doing this: https://github.com/erictrinh/safe-access现在有一个npm模块可以执行此操作: https ://github.com/erictrinh/safe-access

Example usage:示例用法:

var access = require('safe-access');
access(very, 'nested.property.and.array[0]');

While reduce is good, I am surprised no one used forEach:虽然 reduce 很好,但我很惊讶没有人使用 forEach:

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

Test 测试

I'm developing online-shop with React.我正在使用 React 开发在线商店。 I tried to change values in copied state object to update original state with it on submit.我试图更改复制状态对象中的值以在提交时使用它更新原始状态。 Examples above haven't worked for me, because most of them mutate structure of copied object.上面的例子对我不起作用,因为它们中的大多数改变了复制对象的结构。 I found working example of the function for accessing and changing values of the deep nested object properties: https://lowrey.me/create-an-object-by-path-in-javascript-2/ Here it is:我找到了用于访问和更改深层嵌套对象属性值的函数的工作示例: 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;
};

Just had the same question recently and successfully used https://npmjs.org/package/tea-properties which also set nested object/arrays :最近刚刚有同样的问题并成功使用了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'); // true

set:放:

var o = {};

var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');

assert(o.prop.arr[0].foo, 'bar'); // true

Based on a previous answer, I have created a function that can also handle brackets.根据之前的回答,我创建了一个也可以处理括号的函数。 But no dots inside them due to the split.但由于分裂,它们内部没有点。

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

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

Inspired by @webjay's answer: https://stackoverflow.com/a/46008856/4110122受@webjay 回答的启发: https ://stackoverflow.com/a/46008856/4110122

I made this function which can you use it to Get/ Set/ Unset any value in object我做了这个函数,你可以用它来获取/设置/取消设置对象中的任何值

function Object_Manager(obj, Path, value, Action) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

To use it:要使用它:

 // Set
 Object_Manager(Obj,[Level1,Level2,Level3],New_Value, 'Set');

 // Get
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Get');

 // Unset
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');

Extension of Mohamad Hamouday' Answer will fill in missing keys Mohamad Hamouday' Answer 的扩展将填补缺失的密钥

function Object_Manager(obj, Path, value, Action, strict) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            console.log(level,':',a, '|||',b)
            if (!strict){
              if (!(b in a)) a[b] = {}
            }


            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

Example例子


obja = {
  "a": {
    "b":"nom"
  }
}

// Set
path = "c.b" // Path does not exist
Object_Manager(obja,path.split('.'), 'test_new_val', 'Set', false);

// Expected Output: Object { a: Object { b: "nom" }, c: Object { b: "test_new_value" } }

You can use ramda library.您可以使用ramda库。

Learning ramda also helps you to work with immutable objects easily.学习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 https://codepen.io/ghominejad/pen/BayJZOQ

Based on Alnitak's answer .基于 Alnitak 的回答

I wrapped the polyfill in a check, and reduced the function to a single chained reduction.我将 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'))

This can be simplified by splitting the logic into three separate functions:这可以通过将逻辑拆分为三个独立的函数来简化:

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

This variation supports:此变体支持:

  • passing an array or string argument传递数组或字符串参数
  • dealing with undefined values during invocation and execution在调用和执行期间处理undefined的值
  • testing each function independently独立测试每个功能
  • using each function independently独立使用每个功能

What about this solution:这个解决方案怎么样:

setJsonValue: function (json, field, val) {
  if (field !== undefined){
    try {
      eval("json." + field + " = val");
    }
    catch(e){
      ;
    }
  }  
}

And this one, for getting:而这个,用于获得:

getJsonValue: function (json, field){
  var value = undefined;
  if (field !== undefined) {
    try {
      eval("value = json." + field);
    } 
    catch(e){
      ;
    }
  }
  return value;
};

Probably some will consider them unsafe, but they must be much faster then, parsing the string.可能有些人会认为它们不安全,但它们解析字符串时必须快得多。

The solutions here are just for accessing the deeply nested keys.这里的解决方案仅用于访问深度嵌套的键。 I needed one for accessing, adding, modifying and deleting the keys.我需要一个来访问、添加、修改和删除密钥。 This is what I came up with:这就是我想出的:

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 : path in an array. path_to_key :数组中的路径。 You can replace it by your string_path.split(".") .您可以用您的string_path.split(".")替换它。
  • type_of_function : 0 for accessing(dont pass any value to value ), 0 for add and modify. type_of_function : 0 用于访问(不要将任何值传递给value ),0 用于添加和修改。 1 for delete. 1 用于删除。

Building off of Alnitak's answer:基于 Alnitak 的回答:

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, '.$1'); // 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;
  };

} }

This allows you to set a value as well!这也允许您设置一个值!

I've created an npm package and github with this as well我也用这个创建了一个npm 包github

Instead of a string an array can be used adressing nested objects and arrays eg: ["my_field", "another_field", 0, "last_field", 10]可以使用数组代替字符串来处理嵌套对象和数组,例如: ["my_field", "another_field", 0, "last_field", 10]

Here is an example that would change a field based on this array representation.这是一个基于此数组表示更改字段的示例。 I am using something like that in react.js for controlled input fields that change the state of nested structures.我在 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);

Working with Underscore 's property or propertyOf :使用UnderscorepropertypropertyOf

 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>

Good Luck...祝你好运...

React example - Using lodash React 示例 - 使用 lodash

This may not be the most efficient way, from a performance perspective, but if your app is some monolith it sure as heck will save you some time.从性能的角度来看,这可能不是最有效的方法,但如果您的应用程序是单体应用程序,它肯定会为您节省一些时间。 Especially, when you are tightly coupling your state data format to an API back-end.尤其是当您将状态数据格式与 API 后端紧密耦合时。

   import set from "lodash/set";  // More efficient import

    class UserProfile extends Component {

      constructor(props){
        super(props);

        this.state = {
          user: {
            account: {
              id: "",
              email: "",
              first_name: ""
            }
          }
        }
      }

       /**
       * Updates the state based on the form input
       * 
       * @param {FormUpdate} event 
       */
      userAccountFormHook(event) {
        // https://lodash.com/docs#get
        // https://lodash.com/docs#set
        const { name, value } = event.target;
        let current_state = this.state
        set(current_state, name, value)  // Magic happens here
        this.setState(current_state);
      }

    render() {
        return (
          <CustomFormInput
            label: "First Name"
            type: "text"
            placeholder: "First Name"
            name: "user.account.first_name"
            onChange: {this.userAccountFormHook}
            value: {this.state.user.account.first_name}

          />
      )
  }
}

AngularJS has $scope.$eval AngularJS 有$scope.$eval

With AngularJS, one can use the $scope.$eval method to access nested objects:使用 AngularJS,可以使用$scope.$eval方法来访问嵌套对象:

$scope.someObject = someObject;
console.log( $scope.$eval("someObject.part3[0].name") ); //Part 3A

For more information, see有关详细信息,请参阅

The DEMO演示

 angular.module("app",[]) .run(function($rootScope) { $rootScope.someObject = { 'part2' : { 'name': 'Part 2', 'size': '15', 'qty' : '60' }, 'part3' : [{ 'name': 'Part 3A', 'size': '10', 'qty' : '20' },{ name: 'Part 3B' }] }; console.log( "part3[0].name =", $rootScope.$eval("someObject.part3[0].name") ); })
 <script src="//unpkg.com/angular/angular.js"></script> <body ng-app="app" </body>

Note that the following will not work for all valid unicode property names (but neither will any of the other answers as far as I can tell).请注意,以下内容不适用于所有有效的 unicode 属性名称(但据我所知,其他任何答案都不会)。

 const PATTERN = /[\^|\[|\.]([$|\w]+)/gu function propValue(o, s) { const names = [] for(let [, name] of [...s.matchAll(PATTERN)]) names.push(name) return names.reduce((p, propName) => { if(!p.hasOwnProperty(propName)) throw 'invalid property name' return p[propName] }, o) } let path = 'myObject.1._property2[0][0].$property3' let o = { 1: { _property2: [ [{ $property3: 'Hello World' }] ] } } console.log(propValue(o, path)) // 'Hello World'

If you need to access different nested key without knowing it at coding time (it will be trivial to address them) you can use the array notation accessor:如果您需要在编码时访问不同的嵌套键而不知道它(解决它们很简单),您可以使用数组表示法访问器:

var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 =  someObject['part3'][0]['name'];

They are equivalent to the dot notation accessor and may vary at runtime, for example:它们等效于点符号访问器,并且可能在运行时有所不同,例如:

var part = 'part1';
var property = 'name';

var part1name = someObject[part][property];

is equivalent to相当于

var part1name = someObject['part1']['name'];

or或者

var part1name = someObject.part1.name;

I hope this address your question...我希望这能解决你的问题......

EDIT编辑

I won't use a string to mantain a sort of xpath query to access an object value.我不会使用字符串来维护某种 xpath查询来访问对象值。 As you have to call a function to parse the query and retrieve the value I would follow another path (not :由于您必须调用一个函数来解析查询并检索值,因此我将遵循另一条路径(不是:

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

or, if you are uneasy with the apply method或者,如果您对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);

The functions are shorter, clearer, the interpreter check them for you for syntax errors and so on.函数更短、更清晰,解释器会为您检查它们的语法错误等等。

By the way, I feel that a simple assignment made at right time will be sufficent...顺便说一句,我觉得在正确的时间做一个简单的任务就足够了......

Using object-scan this becomes a one liner.使用对象扫描,这变成了一条线。 However more importantly this solution considers performance:然而更重要的是,该解决方案考虑了性能:

  • input traversed once during search (even if multiple keys are queried)搜索过程中遍历了一次输入(即使查询了多个键)
  • parsing only happens once on init (in case multiple objects are queried)解析仅在初始化时发生一次(以防查询多个对象)
  • allow for extended syntax using *允许使用*扩展语法

 // const objectScan = require('object-scan'); const 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' }] }; const get = (haystack, needle) => objectScan([needle], { rtn: 'value', abort: true })(haystack); console.log(get(someObject, 'part1.name')); // => Part 1 console.log(get(someObject, 'part2.qty')); // => 60 console.log(get(someObject, 'part3[0].name')); // => Part 3A const getAll = (haystack, ...needles) => objectScan(needles, { reverse: false, rtn: 'entry', joined: true })(haystack); console.log(getAll(someObject, 'part1.name', 'part2.qty', 'part3[0].name')); /* => [ [ 'part1.name', 'Part 1' ], [ 'part2.qty', '60' ], [ 'part3[0].name', 'Part 3A' ] ] */ console.log(getAll(someObject, 'part1.*')); /* => [ [ 'part1.name', 'Part 1' ], [ 'part1.size', '20' ], [ 'part1.qty', '50' ] ] */
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer : I'm the author of object-scan免责声明:我是对象扫描的作者

My solution is based on that given by @AdrianoSpadoni and addresses a need to clone the object我的解决方案基于@AdrianoSpadoni 给出的解决方案,并解决了克隆对象的需要

function generateData(object: any, path: string, value: any): object {
  const clone = JSON.parse(JSON.stringify(object));
  path
    .split(".")
    .reduce(
    (o, p, i) => (o[p] = path.split(".").length === ++i ? value : o[p] || {}),
  clone
);
  return clone;
}

I've looked on all the other answers, decided to add improvements into more readable code:我查看了所有其他答案,决定将改进添加到更具可读性的代码中:

function getObjectValByString(obj, str) {
if (typeof obj === "string") return obj;

const fields = str.split(".");

return getObjectValByString(obj[fields[0]], fields.slice(1).join("."));}

heres a code snippet:这是一个代码片段:

 let someObject = { partner: { id: "AIM", person: { name: "ANT", an: { name: "ESM" }, }, }, }; function getObjectValByString(obj, str) { if (typeof obj === "string") return obj; const fields = str.split("."); return getObjectValByString(obj[fields[0]], fields.slice(1).join(".")); } const result = getObjectValByString(someObject, "partner.person.an.name"); console.log({ result, });

 DotObject = obj => new Proxy(obj, { get: function (o,k) { let m = k.match(/(.+?)\.(.+)/) return m ? this.get(o[m[1]], m[2]) : o[k] } }) let test = DotObject({a: {b: {c: 'wow'}}}) console.log(test['abc'])

Starting from @Alnitak answer I built this source, which downloads an actual .JSON file and processes it, printing to console explanatory strings for each step, and more details in case of wrong key passed:从@Alnitak answer开始,我构建了这个源,它下载一个实际的 .JSON 文件并对其进行处理,为每个步骤打印到控制台解释性字符串,并在传递错误键的情况下提供更多详细信息:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  <script>
function retrieveURL(url) {
        var client = new XMLHttpRequest();
        prefix = "https://cors-anywhere.herokuapp.com/"
        client.open('GET', prefix + url);
        client.responseType = 'text';
        client.onload = function() {
            response = client.response; // Load remote response.
            console.log("Response received.");
            parsedJSON  = JSON.parse(response);
            console.log(parsedJSON);
            console.log(JSONitemByPath(parsedJSON,"geometry[6].obs[3].latituade"));
            return response;
        };
        try {
            client.send();
        } catch(e) {
            console.log("NETWORK ERROR!");
            console.log(e);
        }
}



function JSONitemByPath(o, s) {
    structure = "";
    originalString = s;
    console.log("Received string: ", s);
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    console.log("Converted to   : ", s);
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');

    console.log("Single keys to parse: ",a);

    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
            console.log("object." + structure +  a[i], o);
            structure +=  a[i] + ".";
        } else {
            console.log("ERROR: wrong path passed: ", originalString);
            console.log("       Last working level: ", structure.substr(0,structure.length-1));
            console.log("       Contents: ", o);
            console.log("       Available/passed key: ");
            Object.keys(o).forEach((prop)=> console.log("       "+prop +"/" + k));
            return;
        }
    }
    return o;
}


function main() {
    rawJSON = retrieveURL("http://haya2now.jp/data/data.json");
}

</script>
  </head>
  <body onload="main()">
  </body>
</html>

Output example:输出示例:

Response received.
json-querier.html:17 {geometry: Array(7), error: Array(0), status: {…}}
json-querier.html:34 Received string:  geometry[6].obs[3].latituade
json-querier.html:36 Converted to   :  geometry.6.obs.3.latituade
json-querier.html:40 Single keys to parse:  (5) ["geometry", "6", "obs", "3", "latituade"]
json-querier.html:46 object.geometry (7) [{…}, {…}, {…}, {…}, {…}, {…}, {…}]
json-querier.html:46 object.geometry.6 {hayabusa2: {…}, earth: {…}, obs: Array(6), TT: 2458816.04973593, ryugu: {…}, …}
json-querier.html:46 object.geometry.6.obs (6) [{…}, {…}, {…}, {…}, {…}, {…}]
json-querier.html:46 object.geometry.6.obs.3 {longitude: 148.98, hayabusa2: {…}, sun: {…}, name: "DSS-43", latitude: -35.4, …}
json-querier.html:49 ERROR: wrong path passed:  geometry[6].obs[3].latituade
json-querier.html:50        Last working level:  geometry.6.obs.3
json-querier.html:51        Contents:  {longitude: 148.98, hayabusa2: {…}, sun: {…}, name: "DSS-43", latitude: -35.4, …}
json-querier.html:52        Available/passed key: 
json-querier.html:53        longitude/latituade
json-querier.html:53        hayabusa2/latituade
json-querier.html:53        sun/latituade
json-querier.html:53        name/latituade
json-querier.html:53        latitude/latituade
json-querier.html:53        altitude/latituade
json-querier.html:18 undefined

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM