将点表示法中的JavaScript字符串转换为对象引用

给定一个JavaScript对象,

var obj = { a: { b: '1', c: '2' } }

和字符串

"a.b"

我怎么把字符串转换成点符号呢

var val = obj.a.b

如果字符串只是'a',我可以使用obj[a]。但这个更复杂。我想应该有什么简单的方法,但现在想不起来了。

198103 次浏览

我不清楚你的问题是什么。给定你的对象,obj.a.b会原样给你“2”。如果你想操纵字符串使用括号,你可以这样做:

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]
var find = function(root, path) {
var segments = path.split('.'),
cursor = root,
target;


for (var i = 0; i < segments.length; ++i) {
target = cursor[segments[i]];
if (typeof target == "undefined") return void 0;
cursor = target;
}


return cursor;
};


var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1


var set = function (root, path, value) {
var segments = path.split('.'),
cursor = root,
target;


for (var i = 0; i < segments.length - 1; ++i) {
cursor = cursor[segments[i]] || { };
}


cursor[segments[segments.length - 1]] = value;
};


set(obj, "a.k", function () { console.log("hello world"); });


find(obj, "a.k")(); // hello world
var a = { b: { c: 9 } };


function value(layer, path, value) {
var i = 0,
path = path.split('.');


for (; i < path.length; i++)
if (value != null && i + 1 === path.length)
layer[path[i]] = value;
layer = layer[path[i]];


return layer;
};


value(a, 'b.c'); // 9


value(a, 'b.c', 4);


value(a, 'b.c'); // 4

与更简单的eval方式相比,这是大量的代码,但就像Simon Willison说的,你不应该使用eval

同时,JSFiddle

这是一个递归的例子。

function recompose(obj, string) {
var parts = string.split('.');
var newObj = obj[parts[0]];
if (parts[1]) {
parts.splice(0, 1);
var newString = parts.join('.');
return recompose(newObj, newString);
}
return newObj;
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};
console.log(recompose(obj, 'a.d.a.b')); //blah

虽然我很高兴这个答案得到了很多赞,但我也有点害怕。如果需要转换点符号字符串,如"x.a.b.c"转换为引用,这可能(可能)表明发生了一些非常错误的事情(除非您正在执行一些奇怪的反序列化)。

也就是说,想要找到这个答案的新手必须问自己一个问题:“我为什么要这样做?”

当然,如果您的用例很小,并且不会遇到性能问题,并且您不需要在抽象的基础上构建以使其更复杂,那么这样做通常是好的。事实上,如果这将降低代码复杂性并使事情简单,应该可能会继续执行OP要求的操作。然而,如果情况并非如此,请考虑以下是否适用:

案例1:作为处理数据的主要方法(例如,作为应用程序传递对象和取消引用对象的默认形式)。比如问“如何从字符串中查找函数或变量名”。

  • 这是一种糟糕的编程实践(特别是不必要的元编程,并且违反了无函数副作用的编码风格,并且会影响性能)。遇到这种情况的新手,应该考虑使用数组表示,例如['x','a','b','c'],或者在可能的情况下更直接/简单/直接的东西:比如不要在一开始就失去对引用本身的跟踪(如果只是客户端或服务器端,这是最理想的),等等(添加一个预先存在的唯一id将是不优雅的,但如果规范要求它的存在,则可以使用)。

案例2:处理序列化数据或将显示给用户的数据。比如用日期作为字符串“1999-12-30”;而不是Date对象(如果不小心,可能会导致时区错误或增加序列化复杂性)。或者你知道自己在做什么。

  • 这可能没问题。注意没有点字符串";在经过消毒的输入片段中。

如果你发现自己一直在使用这个答案,并在字符串和数组之间来回转换,你可能处于糟糕的情况,应该考虑另一种选择。

下面是一个优雅的单行程序,比其他解决方案短10倍:

function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)

[编辑]在ECMAScript 6中:

'a.b.etc'.split('.').reduce((o,i)=> o[i], obj)

(并不是说我认为eval总是像别人说的那样不好(尽管它通常是),然而那些人会很高兴这个方法不使用eval。在给定obj和字符串"a.b.etc"的情况下,上面将找到obj.a.b.etc。)

为了回应那些仍然害怕使用reduce的人,尽管它在ECMA-262标准(第5版)中,这里有一个两行的递归实现:

function multiIndex(obj,is) {  // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) {   // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')

根据JS编译器正在进行的优化,您可能希望确保任何嵌套函数不会在每次调用时通过常规方法重新定义(将它们放置在闭包、对象或全局名称空间中)。

编辑:

在评论中回答一个有趣的问题:

如何将其转换为setter呢?不仅按路径返回值,而且还设置它们,如果一个新值被发送到函数?- Swader 6月28日21:42

(旁注:遗憾的是不能返回带有Setter的对象,因为那样会违反调用约定;评论者似乎转而引用了带有副作用的一般setter样式函数,比如index(obj,"a.b.etc", value)obj.a.b.etc = value。)

reduce样式并不真正适用于此,但我们可以修改递归实现:

function index(obj,is, value) {
if (typeof is == 'string')
return index(obj,is.split('.'), value);
else if (is.length==1 && value!==undefined)
return obj[is[0]] = value;
else if (is.length==0)
return obj;
else
return index(obj[is[0]],is.slice(1), value);
}

演示:

> obj = {a:{b:{etc:5}}}


> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc'])   #works with both strings and lists
5


> index(obj,'a.b.etc', 123)    #setter-mode - third argument (possibly poor form)
123


> index(obj,'a.b.etc')
123

...虽然我个人建议创建一个单独的函数setIndex(...)。我想在一个旁注上结束,这个问题的最初提出者可以(应该?)使用索引数组(他们可以从.split中获得),而不是字符串;尽管方便函数通常没什么问题。


一位评论者问道:

数组呢?比如“a.b[4].c.d[1][2][3]”;? -AlexS

Javascript是一种非常奇怪的语言;一般情况下,对象只能以字符串作为属性键,因此,例如,如果x是像x={}这样的泛型对象,则x[1]将变成x["1"]…你没看错……是的……

Javascript数组(本身就是Object的实例)特别鼓励使用整型键,即使你可以使用类似x=[]; x["puppy"]=5;的方法。

但通常(也有例外),x["somestring"]===x.somestring(当它被允许时;你不能做x.123)。

(请记住,无论你使用什么JS编译器,如果它能证明它不会违反规范,可能会选择将这些编译成更合理的表示。)

因此,您的问题的答案将取决于您是否假设这些对象只接受整数(由于问题域中的限制)。让我们假设没有。那么一个有效的表达式是一个基本标识符加上一些__abc0加上一些__abc1的串联。

让我们暂时忽略,我们当然可以在语法中合法地做其他事情,如identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN];整数不是那样的“特殊”。

评论者的声明将等效于a["b"][4]["c"]["d"][1][2][3],尽管我们可能也应该支持a.b["c\"validjsstringliteral"][3]。你必须检查关于字符串字面量的Ecmascript语法部分,看看如何解析一个有效的字符串文字。从技术上讲,你还想检查(不像我的第一个答案)a是一个有效的javascript标识符

对于你的问题如果字符串不包含逗号或括号,一个简单的答案就是匹配长度为1+的不在,[]集合中的字符序列:

> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^  ^ ^^^ ^  ^   ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]

如果字符串不包含转义字符或"字符,因为IdentifierNames是StringLiterals的子语言(我认为??),你可以先将你的点转换为[]:

> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g;
match=matcher.exec(demoString); ) {
R.push(Array.from(match).slice(1).filter(x=> x!==undefined)[0]);
// extremely bad code because js regexes are weird, don't use this
}
> R


["abc", "4", "c", "def", "1", "2", "gh"]

当然,一定要小心,永远不要相信你的数据。在某些用例中可能有效的一些坏方法还包括:

// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.:
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3"  //use code from before

2018年特别编辑:

让我们绕一圈,使用我们能想到的最低效、最可怕的超元编程解决方案……为了语法纯度hamfistery的利益。使用ES6代理对象!让我们还定义一些属性,这些属性(恕我直言)可能会破坏不正确编写的库。如果你关心性能、理智(你自己或别人的)、工作等,你应该谨慎使用这个选项。

// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub


// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization


// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
get: function(obj,key, proxy) {
return key.split('.').reduce((o,i)=> o[i], obj);
},
set: function(obj,key,value, proxy) {
var keys = key.split('.');
var beforeLast = keys.slice(0,-1).reduce((o,i)=> o[i], obj);
beforeLast[keys[-1]] = value;
},
has: function(obj,key) {
//etc
}
};
function hyperIndexOf(target) {
return new Proxy(target, hyperIndexProxyHandler);
}

演示:

var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));


var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));


console.log("(behind the scenes) objHyper is:", objHyper);


if (!({}).H)
Object.defineProperties(Object.prototype, {
H: {
get: function() {
return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
}
}
});


console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);

输出:

obj是:{“a":{“b":{“c": 1、“d": 2}}}

(代理覆盖get) objHyper['a.b.c']是:1

(代理覆盖集)objHyper [' a.b.c ') = 3,现在obj是:{“a":{“b":{“c": 3,“d": 2}}}

(幕后)objHyper是:代理{a:{…}}

(快捷)obj.H [' a.b.c '] = 4

(快捷)obj.H [' a.b.c '] obj [a] [b] [' c ']: 4

低效的想法:你可以根据输入参数修改上面的调度;或者使用.match(/[^\]\[.]+/g)方法来支持obj['keys'].like[3]['this'],或者如果使用instanceof Array,则只接受数组作为输入,如keys = ['a','b','c']; obj.H[keys]


每个建议,也许你想要以一种“更软的”nan风格的方式处理未定义的索引(例如,index({a:{b:{c:...}}}, 'a.x.c')返回undefined而不是uncaught TypeError)…:

  1. 从“我们应该返回undefined而不是抛出错误”的角度来看,这是有意义的;在一维索引情况下({})['例如。']==undefined,因此“我们应该返回undefined而不是抛出错误”;在n维空间中。

  2. 从我们正在执行x['a']['x']['c']的角度来看,是有意义的,在上面的例子中,它会以TypeError失败。

也就是说,你可以通过将你的约简函数替换为:

< p > (o,i)=> o===undefined?undefined:o[i](o,i)=> (o||{})[i] . < / p >

(你可以通过使用for循环,并在你的下一个索引的子结果是未定义的时候中断/返回,或者如果你希望这样的失败足够少,使用try-catch来提高效率。)

其他的建议有点晦涩难懂,所以我想我应该贡献一下:

Object.prop = function(obj, prop, val){
var props = prop.split('.')
, final = props.pop(), p
while(p = props.shift()){
if (typeof obj[p] === 'undefined')
return undefined;
obj = obj[p]
}
return val ? (obj[final] = val) : obj[final]
}


var obj = { a: { b: '1', c: '2' } }


// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }

我已经扩展了优雅的由ninjagecko回答,这样函数就可以处理点和/或数组样式的引用,所以一个空字符串就会返回父对象。

给你:

string_to_ref = function (object, reference) {
function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
return !reference ? object : reference.split('.').reduce(dot_deref, object);
};

在这里查看我的jsFiddle工作示例:http://jsfiddle.net/sc0ttyd/q7zyd/

你可以通过点表示法获得对象成员的值,只需一行代码:

new Function('_', 'return _.' + path)(obj);

对你来说:

var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);

为了简化,你可以这样写一个函数:

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

解释:

Function构造函数创建一个新的Function对象。在JavaScript中,每个函数实际上都是一个function对象。使用function构造函数显式创建函数的语法如下:

new Function ([arg1[, arg2[, ...argN]],] functionBody)

其中arguments(arg1 to argN)必须是一个对应于有效javaScript标识符的字符串,functionBody是一个包含包含函数定义的javaScript语句的字符串。

在我们的例子中,我们利用字符串函数体来检索点表示法的对象成员。

希望能有所帮助。

这是我的代码没有使用eval。这也很容易理解。

function value(obj, props) {
if (!props)
return obj;
var propsArr = props.split('.');
var prop = propsArr.splice(0, 1);
return value(obj[prop], propsArr.join('.'));
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};


console.log(value(obj, 'a.d.a.b')); // Returns blah

如果希望多次解引用同一路径,则为每个点符号路径构建函数实际上具有迄今为止最好的表现(扩展了James Wilkins在上面评论中链接到的性能测试)。

var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);

在安全性和最坏情况下的性能方面,使用Function构造函数与eval()有一些相同的缺点,但在我看来,对于需要结合极端动态和高性能的情况,它是一个没有得到充分利用的工具。我使用这种方法来构建数组过滤器函数,并在AngularJS摘要循环中调用它们。我的配置文件始终显示,array.filter()步骤用不到1毫秒的时间来解引用和过滤大约2000个复杂对象,使用3-4级深的动态定义路径。

当然,类似的方法也可以用于创建setter函数:

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");

注意,如果你已经在使用Lodash,你可以使用propertyget函数:

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

Underscore.js也有一个property函数,但它不支持点表示法。

是的,扩展基本原型通常不是一个好主意,但是,如果你把所有的扩展放在一个地方,它们可能是有用的。 下面是我的方法

   Object.defineProperty(Object.prototype, "getNestedProperty", {
value     : function (propertyName) {
var result = this;
var arr = propertyName.split(".");


while (arr.length && result) {
result = result[arr.shift()];
}


return result;
},
enumerable: false
});

现在,您将能够获得嵌套的属性,而无需导入模块与函数或复制/粘贴函数。

例子:

{a:{b:11}}.getNestedProperty('a.b'); // Returns 11

在我的项目中Next.js扩展破坏了猫鼬。此外,我还读到它可能会破坏jQuery。所以,永远不要用Next.js的方式:

 Object.prototype.getNestedProperty = function (propertyName) {
var result = this;
var arr = propertyName.split(".");


while (arr.length && result) {
result = result[arr.shift()];
}


return result;
};

这是我的实现

实现1

Object.prototype.access = function() {
var ele = this[arguments[0]];
if(arguments.length === 1) return ele;
return ele.access.apply(ele, [].slice.call(arguments, 1));
}

实现2(使用数组reduce而不是slice)

Object.prototype.access = function() {
var self = this;
return [].reduce.call(arguments,function(prev,cur) {
return prev[cur];
}, self);
}

例子:

var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};


myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11

它也可以处理数组中的对象

var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'

这是我的扩展解决方案由忍子提议

对我来说,简单的字符串表示法是不够的,所以下面的版本支持如下内容:

index(obj, 'data.accounts[0].address[0].postcode');

 

/**
* Get object by index
* @supported
* - arrays supported
* - array indexes supported
* @not-supported
* - multiple arrays
* @issues:
*  index(myAccount, 'accounts[0].address[0].id') - works fine
*  index(myAccount, 'accounts[].address[0].id') - doesnt work
* @Example:
* index(obj, 'data.accounts[].id') => returns array of id's
* index(obj, 'data.accounts[0].id') => returns id of 0 element from array
* index(obj, 'data.accounts[0].addresses.list[0].id') => error
* @param obj
* @param path
* @returns {any}
*/
var index = function(obj, path, isArray?, arrIndex?){


// is an array
if(typeof isArray === 'undefined') isArray = false;
// array index,
// if null, will take all indexes
if(typeof arrIndex === 'undefined') arrIndex = null;


var _arrIndex = null;


var reduceArrayTag = function(i, subArrIndex){
return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
var tmp = i.match(/(\[)([\d]{0,})(\])/);
isArray = true;
if(subArrIndex){
_arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
}else{
arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
}
return '';
});
}


function byIndex(obj, i) {
// if is an array
if(isArray){
isArray = false;
i = reduceArrayTag(i, true);
// if array index is null,
// return an array of with values from every index
if(!arrIndex){
var arrValues = [];
_.forEach(obj, (el) => {
arrValues.push(index(el, i, isArray, arrIndex));
})
return arrValues;
}
// if array index is specified
var value = obj[arrIndex][i];
if(isArray){
arrIndex = _arrIndex;
}else{
arrIndex = null;
}
return value;
}else{
// remove [] from notation,
// if [] has been removed, check the index of array
i = reduceArrayTag(i, false);
return obj[i]
}
}


// reduce with the byIndex method
return path.split('.').reduce(byIndex, obj)
}

如果你可以使用Lodash,有一个函数,它可以做到这一点:

_。get(object, path, [defaultValue]) . get(object, path, [defaultValue]

var val = _.get(obj, "a.b");

我从里卡多·托马西的回答中复制了以下内容,并在必要时修改为创建尚不存在的子对象。它的效率有点低(更多的__abc0和创建空对象),但应该很好。

此外,它将允许我们做Object.prop(obj, 'a.b', false),这是我们以前不能做的。不幸的是,它仍然不允许我们分配undefined…我还不知道该怎么做。

/**
* Object.prop()
*
* Allows dot-notation access to object properties for both getting and setting.
*
* @param {Object} obj    The object we're getting from or setting
* @param {string} prop   The dot-notated string defining the property location
* @param {mixed}  val    For setting only; the value to set
*/
Object.prop = function(obj, prop, val){
var props = prop.split('.'),
final = props.pop(),
p;


for (var i = 0; i < props.length; i++) {
p = props[i];
if (typeof obj[p] === 'undefined') {
// If we're setting
if (typeof val !== 'undefined') {
// If we're not at the end of the props, keep adding new empty objects
if (i != props.length)
obj[p] = {};
}
else
return undefined;
}
obj = obj[p]
}
return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
}

距离最初的帖子已经很多年了。 现在有一个很棒的库叫做object-path。 https://github.com/mariocasciaro/object-path < / p >

在NPM和BOWER上可用 https://www.npmjs.com/package/object-path < / p >

很简单:

objectPath.get(obj, "a.c.1");  //returns "f"
objectPath.set(obj, "a.j.0.f", "m");

并且适用于深度嵌套的属性和数组。

我建议分割路径,迭代它,减少你拥有的对象。这个建议工作使用默认值为缺失的属性。

const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);


console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));

冒着白费口舌的风险… 我发现这在遍历嵌套对象以引用相对于基对象或具有相同结构的类似对象的位置时非常有用。为此,这对于嵌套的对象遍历函数很有用。注意,我使用了一个数组来保存路径。将其修改为使用字符串路径或数组是很简单的。还需要注意的是,您可以将“undefined”赋值给该值,这与其他一些实现不同

/*
* Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
* on each. The path is an array of the keys required to get to curObject from
* baseObject using objectPath(). If the call to fn() returns falsey, objects below
* curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
* The third and fourth arguments are only used by recursion.
*/
function objectTraverse (o, fn, base, path) {
path = path || [];
base = base || o;
Object.keys(o).forEach(function (key) {
if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
path.push(key);
objectTraverse(o[key], fn, base, path);
path.pop();
}
});
}


/*
* Get/set a nested key in an object. Path is an array of the keys to reference each level
* of nesting. If value is provided, the nested key is set.
* The value of the nested key is returned.
*/
function objectPath (o, path, value) {
var last = path.pop();


while (path.length && o) {
o = o[path.shift()];
}
if (arguments.length < 3) {
return (o? o[last] : o);
}
return (o[last] = value);
}

我在我的项目中使用了这个代码

const getValue = (obj, arrPath) => (
arrPath.reduce((x, y) => {
if (y in x) return x[y]
return {}
}, obj)
)

用法:

const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104

几年后,我发现它可以处理范围和数组。例如a['b']["c"].d.etc

function getScopedObj(scope, str) {
let obj=scope, arr;


try {
arr = str.split(/[\[\]\.]/) // split by [,],.
.filter(el => el)             // filter out empty one
.map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
arr.forEach(el => obj = obj[el])
} catch(e) {
obj = undefined;
}


return obj;
}


window.a = {b: {c: {d: {etc: 'success'}}}}


getScopedObj(window, `a.b.c.d.etc`)             // success
getScopedObj(window, `a['b']["c"].d.etc`)       // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined

你可以使用lodash.get

在安装(npm i lodash.get)之后,像这样使用它:

const get = require('lodash.get');


const myObj = {
user: {
firstName: 'Stacky',
lastName: 'Overflowy',
list: ['zero', 'one', 'two']
},
id: 123
};


console.log(get(myObj, 'user.firstName')); // outputs Stacky
console.log(get(myObj, 'id'));             // outputs 123
console.log(get(myObj, 'user.list[1]'));   // outputs one


// You can also update values
get(myObj, 'user').firstName = 'John';

如果你想将任何包含点符号键的对象转换为这些键的排列的版本,你可以使用这个。


这将转换为

{
name: 'Andy',
brothers.0: 'Bob'
brothers.1: 'Steve'
brothers.2: 'Jack'
sisters.0: 'Sally'
}

{
name: 'Andy',
brothers: ['Bob', 'Steve', 'Jack']
sisters: ['Sally']
}

convertDotNotationToArray(objectWithDotNotation) {


Object.entries(objectWithDotNotation).forEach(([key, val]) => {


// Is the key of dot notation
if (key.includes('.')) {
const [name, index] = key.split('.');


// If you have not created an array version, create one
if (!objectWithDotNotation[name]) {
objectWithDotNotation[name] = new Array();
}


// Save the value in the newly created array at the specific index
objectWithDotNotation[name][index] = val;
// Delete the current dot notation key val
delete objectWithDotNotation[key];
}
});


}

使用数组缩减函数将获得/设置基于提供的路径。

我用a.b.c和a.b.2.c {a:{b:[0,1,{c:7}]}}测试了它,它既可以获得键,也可以将对象更改为设定值

cheerz

    function setOrGet(obj, path=[], newValue){
const l = typeof path === 'string' ? path.split('.') : path;
return l.reduce((carry,item, idx)=>{
const leaf = carry[item];
       

// is this last item in path ? cool lets set/get value
if( l.length-idx===1)  {
// mutate object if newValue is set;
carry[item] = newValue===undefined ? leaf : newValue;
// return value if its a get/object if it was a set
return newValue===undefined ? leaf : obj ;
}
    

carry[item] = leaf || {}; // mutate if key not an object;
return carry[item]; // return object ref: to continue reduction;
}, obj)
}
    

   

console.log(
setOrGet({a: {b:1}},'a.b') === 1 ||
'Test Case: Direct read failed'
)
    

console.log(
setOrGet({a: {b:1}},'a.c',22).a.c===22 ||
'Test Case: Direct set failed'
)
    

console.log(
setOrGet({a: {b:[1,2]}},'a.b.1',22).a.b[1]===22 ||
'Test Case: Direct set on array failed'
)
    

console.log(
setOrGet({a: {b:{c: {e:1} }}},'a.b.c.e',22).a.b.c. e===22 ||
'Test Case: deep get failed'
)
    

// failed !. Thats your homework :)
console.log(
setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'a.b.c.e.3 ',22)
)
    

    

    

我个人的建议。

除非没有别的办法,否则不要用这种东西!

我看到很多例子,人们用它来翻译json;所以你会看到类似locale('app.homepage.welcome')的函数。这太糟糕了。如果你已经在object/json中有数据;你知道路径。然后直接使用它,例如locale().app.homepage.welcome,通过改变你的函数返回对象,你得到类型安全,自动完成,不容易出现错别字。

你可以使用npm中提供的库,它简化了这个过程。https://www.npmjs.com/package/dot-object

 var dot = require('dot-object');


var obj = {
some: {
nested: {
value: 'Hi there!'
}
}
};


var val = dot.pick('some.nested.value', obj);
console.log(val);


// Result: Hi there!

使用object-scan似乎有点过度,但你可以简单地这样做

// const objectScan = require('object-scan');


const get = (obj, p) => objectScan([p], { abort: true, rtn: 'value' })(obj);


const obj = { a: { b: '1', c: '2' } };


console.log(get(obj, 'a.b'));
// => 1


console.log(get(obj, '*.c'));
// => 2
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.7.1"></script>

免责声明:我是object-scan的作者

自述中有很多更高级的例子。

如果你想以最快的方式做到这一点,同时处理路径解析或属性解析的任何问题,请检查path-value

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


const value = resolveValue(obj, 'a.b.c');

该库是100%的TypeScript,适用于NodeJS +所有web浏览器。而且它是完全可扩展的,如果你愿意,你可以使用低层的resolvePath,并以自己的方式处理错误。

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


const res = resolvePath(obj, 'a.b.c'); //=> low-level parsing result descriptor

如果你想将一个字符串点表示法转换为对象,我已经做了一个方便的小助手,它可以转换像a.b.c.d这样的字符串,其值为edotPathToObject("a.b.c.d", "value")返回如下:

  {
"a": {
"b": {
"c": {
"d": "value"
}
}
}
}

https://gist.github.com/ahallora/9731d73efb15bd3d3db647efa3389c12

这是其中一种情况,你问10个开发人员,你会得到10个答案。

下面是我使用动态规划的OP[简化]解决方案。

其思想是,您将传递一个希望更新的现有DTO对象。这使得该方法在具有多个输入元素的表单的情况下最有用,这些输入元素的名称属性设置为圆点(fluent)语法。

使用示例:

<input type="text" name="person.contact.firstName" />

代码片段:

const setFluently = (obj, path, value) => {
if (typeof path === "string") {
return setFluently(obj, path.split("."), value);
}


if (path.length <= 1) {
obj[path[0]] = value;
return obj;
}


const key = path[0];
obj[key] = setFluently(obj[key] ? obj[key] : {}, path.slice(1), value);
return obj;
};


const origObj = {
a: {
b: "1",
c: "2"
}
};


setFluently(origObj, "a.b", "3");
setFluently(origObj, "a.c", "4");


console.log(JSON.stringify(origObj, null, 3));

function at(obj, path, val = undefined) {
// If path is an Array,
if (Array.isArray(path)) {
// it returns the mapped array for each result of the path
return path.map((path) => at(obj, path, val));
}
// Uniting several RegExps into one
const rx = new RegExp(
[
/(?:^(?:\.\s*)?([_a-zA-Z][_a-zA-Z0-9]*))/,
/(?:^\[\s*(\d+)\s*\])/,
/(?:^\[\s*'([^']*(?:\\'[^']*)*)'\s*\])/,
/(?:^\[\s*"([^"]*(?:\\"[^"]*)*)"\s*\])/,
/(?:^\[\s*`([^`]*(?:\\`[^`]*)*)`\s*\])/,
]
.map((r) => r.source)
.join("|")
);
let rm;
while (rm = rx.exec(path.trim())) {
// Matched resource
let [rf, rp] = rm.filter(Boolean);
// If no one matches found,
if (!rm[1] && !rm[2]) {
// it will replace escape-chars
rp = rp.replace(/\\(.)/g, "$1");
}
// If the new value is set,
if ("undefined" != typeof val && path.length == rf.length) {
// assign a value to the object property and return it
return (obj[rp] = val);
}
// Going one step deeper
obj = obj[rp];
// Removing a step from the path
path = path.substr(rf.length).trim();
}
if (path) {
throw new SyntaxError();
}
return obj;
}


// Test object schema
let o = { a: { b: [ [ { c: { d: { '"e"': { f: { g: "xxx" } } } } } ] ] } };


// Print source object
console.log(JSON.stringify(o));


// Set value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]', "zzz"));


// Get value
console.log(at(o, '.a["b"][0][0].c[`d`]["\\"e\\""][\'f\']["g"]'));


// Print result object
console.log(JSON.stringify(o));

我想把这个说出来:

function propertyByPath(object, path = '') {
if (/[,(){}&|;]/.test(path)) {
throw 'forbidden characters in path';
}
return Function(
...Object.keys(window).filter(k => window[k] instanceof Window || window[k] instanceof Document),
"obj",
`return ((o) => o${!path.startsWith('[') ? '.' : ''}${path})(...arguments, obj);`)
.bind(object)(object);
}
propertyByPath({ a: { b: 'hello1' } }, "a.b"); // prints 'hello1'
propertyByPath({ a: { b: 'hello2' } }, "['a']?.b"); // returns 'hello2'
propertyByPath({ a: { b: 'hello2' } }, "a.b;console.log()"); // throws exception

上面的代码在执行路径计算和屏蔽Window和Document对象(如果执行阻止不成功的话)的同时,努力防止代码执行。

我并不是说它是100%安全的(尽管我希望看到建议可能绕过的评论)也不是有效的,但在需要路径的灵活性(可选链等)以及一些缓解措施的情况下,它可能是合适的。

2021

您不需要每次希望在程序中添加新功能时都拉入另一个依赖项。现代JS非常强大,optional-chaining运营商 ?.现在得到了广泛的支持,使得这类任务变得非常简单。

用一行代码就可以编写get,它接受一个输入对象t和字符串path。它适用于任何嵌套级别的对象而且数组

const get = (t, path) =>
path.split(".").reduce((r, k) => r?.[k], t)
  

const mydata =
{ a: { b: [ 0, { c: { d: [ "hello", "world" ] } } ] } }


console.log(get(mydata, "a.b.1.c.d.0"))
console.log(get(mydata, "a.b.1.c.d.1"))
console.log(get(mydata, "a.b.x.y.z"))

"hello"
"world"
undefined

使用这个函数:

function dotToObject(data) {
function index(parent, key, value) {
const [mainKey, ...children] = key.split(".");
parent[mainKey] = parent[mainKey] || {};


if (children.length === 1) {
parent[mainKey][children[0]] = value;
} else {
index(parent[mainKey], children.join("."), value);
}
}


const result = Object.entries(data).reduce((acc, [key, value]) => {
if (key.includes(".")) {
index(acc, key, value);
} else {
acc[key] = value;
}


return acc;
}, {});
return result;
}


module.exports = { dotToObject };

例:

const user = {
id: 1,
name: 'My name',
'address.zipCode': '123',
'address.name': 'Some name',
'address.something.id': 1,
}


const mappedUser = dotToObject(user)
console.log(JSON.stringify(mappedUser, null, 2))

输出:

{
"id": 1,
"name": "My name",
"address": {
"zipCode": "123",
"name": "Some name",
"something": {
"id": 1
}
}
}

解决方案:

function deepFind(key, data){
return key.split('.').reduce((ob,i)=> ob[i], data)
}

用法:

const obj = {
company: "Pet Shop",
person: {
name: "John"
},
animal: {
name: "Lucky"
}
}


const company = deepFind("company", obj)
const personName = deepFind("person.name", obj)
const animalName = deepFind("animal.name", obj)