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

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

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的值填充,即“Part 1”。part2quantity也是一样,它的容量是60。

有没有办法实现这与纯javascript或JQuery?

337298 次浏览

我认为你的要求是:

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

如果你需要访问不同的嵌套键,而不知道它在编码时(这将是微不足道的),你可以使用数组符号访问器:

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

或者,如果您不习惯使用应用方法

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

函数更短,更清晰,解释器会为你检查语法错误等等。

顺便说一下,我觉得在适当的时候做一个简单的任务就足够了……

你必须自己解析字符串:

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

它使解析更容易。

DEMO

我只是基于一些类似的代码,我已经有了,它似乎工作:

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

使用eval:

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

换行以在错误时返回undefined

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

http://jsfiddle.net/shanimal/b3xTw/ < a href = " http://jsfiddle.net/shanimal/b3xTw/ " > < / >

在使用评估的权力时,请使用常识和谨慎。它有点像光剑,如果你打开它,有90%的几率你会切断一个肢体。并不是每个人都适合。

也适用于对象内的数组/数组。

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

刚刚有同样的问题,最近成功地使用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

这个解决方案怎么样:

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

这个,用来得到:

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

可能有些人会认为它们不安全,但它们解析字符串的速度肯定要快得多。

这是我使用的解决方案:

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

限制:

  • 不能将括号([])用于数组下标——尽管在分隔符令牌之间指定数组下标(例如,.)可以正常工作,如上所示。

现在有一个npm模块来做这件事:https://github.com/erictrinh/safe-access

使用示例:

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

在这里我提供了更多的方法,这些方法在很多方面看起来都更快:

选项1:Split string on。Or [Or] Or ' Or ",颠倒过来,跳过空项。

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):低级字符扫描(没有regex/split/等,只是一个快速字符扫描)。 注意:这个不支持索引的引号

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:(new:选项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: # EYZ0

“eval(…)”仍然是王者(即性能方面)。如果直接控制属性路径,使用'eval'应该没有任何问题(特别是在要求速度的情况下)。如果拉属性路径“在电线”(在线上!?哈哈:P),那么是的,使用其他安全的东西。只有白痴才会说永远不要使用“eval”,因为使用它的时候有是很好的理由。另外,“它在Doug Crockford的JSON解析器中使用。”如果输入是安全的,那么就没有任何问题。用正确的工具做正确的工作,就是这样。

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

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

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

在你的情况下,从someObject获取part1.name的值只需做:

objectGet(someObject, 'part1.name');

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

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

现在lodash使用_.get(obj, property)支持这一点。看到# EYZ1

文档中的例子:

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'
/**
* 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")

这里的解决方案仅用于访问深度嵌套的键。我需要一个来访问、添加、修改和删除密钥。这是我想到的:

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:数组中的路径。你可以用string_path.split(".")替换它。
  • type_of_function: 0访问(不传递任何值给value), 0添加和修改。1表示删除。

这是一个有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版本w/ reduce…

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

< a href = " https://plnkr。co/edit/NKb6SE" rel="noreferrer" title="Plunkr">Plunkr .

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

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

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

# EYZ0:

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

对于一个也识别false, 0和负数并接受默认值作为参数的ready to use函数:

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

# EYZ0:

对于路径(由@ robo -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}}}

# EYZ0:

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

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

这也允许你设置一个值!

我也创建了npm包github

数组可以代替字符串来处理嵌套对象和数组,例如:["my_field", "another_field", 0, "last_field", 10]

下面是一个基于该数组表示方式更改字段的示例。我在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);

基于前面的回答,我创建了一个也可以处理括号的函数。但由于裂开,里面没有点。

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

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

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

< a href = " https://www.typescriptlang.org/play/ src = % 0阿瓦尔人% 20测试% % 20对象3 % 20% 3 d % 20% 7 b % 0 a % 20 20% 20% 20% % % 7 bb % 3 20% % 3 7 bc % % 22 3 d % 22% 7 d % 7 d % 0 a % 7 d % 0 a % 0机能缺失% 20 valueForKeyPath (obj % % 20对象% 2 3 3 c % 20路% % 20字符串)% 3 aany 7 b % 0 a % % 20% 20% 20% 20% 20% 20% 20% 20% 20 const % 20键% 20% 3 d % 20 path.split(& # 39; # 39;公司)% 3 b % 0 a % 20% 20% 20% 20% 20% 20% 20% 20 keys.foreach((关键)% 3 d % 3 e % 20 obj % 20% 3 d % 20 obj % 5 bkey % 5 d) % 3 b % 0 a % 20% 20% 20% 20% 20% 20% 20% 20返回% 20 obj % 3 b % 0 a % 20% 20% 20% 20% 7 d % 3 b % 0 a % 0 aalert (valueForKeyPath(测试% 2 c % 20 & # 39; a.b.c # 39;))”测试rel = " nofollow noreferrer " > < / >

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

使用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...

这可能永远都不会曝光……但不管怎样,它在这里。

  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

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

# EYZ0错误

使用数组缩减访问嵌套对象

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

const user = {
id: 101,
email: 'jack@dev.com',
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;

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

灵感来自@webjay的回答: # EYZ0 < / p >

我做了这个函数,你可以用它来获取/设置/取消设置对象中的任何值

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

使用它:

 // 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');
我正在用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;
};

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


例子


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


React示例-使用lodash

从性能角度来看,这可能不是最有效的方法,但如果你的应用是一个庞然大物,它肯定会为你节省一些时间。特别是当您将状态数据格式与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}


/>
)
}
}

从@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>

输出的例子:

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

您可以使用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);




< a href = " https://codepen。io/ghominejad/pen/BayJZOQ" rel="nofollow noreferrer">https://codepen.io/ghominejad/pen/BayJZOQ

AngularJS有$scope.$eval

在AngularJS中,你可以使用$scope.$eval方法来访问嵌套对象:

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

有关更多信息,请参见

演示

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>

基于Alnitak的回答

我把填充材料包在格子里,将函数简化为单链还原。

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

请注意,以下选项并不适用于所有有效的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'

如果您想要一个能够正确检测和报告路径解析中任何问题的详细信息的解决方案,我为此编写了自己的解决方案——库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

注意,对于索引,我们使用.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

使用object-scan就变成了一行代码。然而,更重要的是,这个解决方案考虑性能:

  • 在搜索期间遍历一次输入(即使查询了多个键)
  • 解析只在init上发生一次(如果查询多个对象)
  • 允许使用*扩展语法

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

免责声明:我是EYZ1的作者

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

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
  • 独立测试每个功能
  • 单独使用每个函数

而不是尝试模拟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];
};

其他功能的演示:
# EYZ0 < / p > .set(/.del(bob = 是不必要的,除非你的路径可能是空的(操作根对象) 我证明我没有克隆对象使用steve来保持对原始的引用,并在第一个.set(

之后检查bob == steve //true

我的解决方案是基于@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;
}

我已经看了所有其他的答案,决定在更可读的代码中添加改进:

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


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


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

下面是一个代码片段:

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