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

标签 javascript

给定一个JS对象

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


和一个字符串

"a.b"


如何将字符串转换为点表示法,以便我可以

var val = obj.a.b


如果字符串只是'a',则可以使用obj[a]。但这更复杂。我想有一种简单的方法,但是目前可以逃脱。

最佳答案

最近的说明:虽然我很高兴这个答案获得了很多好评,但我还是有些恐惧。如果需要将点符号字符串(例如“ x.a.b.c”)转换为引用,则可能(可能)是表明发生了非常错误的迹象(除非您正在执行一些奇怪的反序列化)。
  
  也就是说,寻找答案的新手必须问自己一个问题:“我为什么要这样做?”
  
  情况1:作为处理数据的主要方法(例如,作为应用程序传递对象并取消引用对象的默认形式)。就像问“如何从字符串中查找函数或变量名”一样。
  
  
  这是不好的编程习惯(特别是不必要的元编程,并且这违反了函数的无副作用编码风格,并且会对性能造成影响)。在这种情况下发现自己的新手,应该考虑使用数组表示形式,例如['x','a','b','c'],甚至可能更直接/简单/直接的内容:就像一开始就不迷失引用本身(最理想的情况是,如果它只是客户端, (仅在服务器端或仅在服务器端)等等。(预先存在的唯一ID很难添加,但是如果规范另外要求它存在则可以使用。)
  
  
  情况2:使用序列化数据或将显示给用户的数据。就像使用日期作为字符串“ 1999-12-30”而不是使用Date对象一样(如果不小心,可能会导致时区错误或增加序列化复杂性)。
  
  
  这也许很好。请注意,没有点串“。”在您清理过的输入片段中。
  
  
  如果您发现自己一直在使用此答案并在字符串和数组之间来回转换,则可能情况很糟糕,应考虑使用其他方法。


这是一个优雅的单缸套,比其他解决方案短十倍:

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


[edit]或在ECMAScript 6中:

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


(并不是我认为eval总是像其他人认为的那样总是很糟糕(尽管通常是这样),尽管如此,那些人会为这种方法不使用eval而感到高兴。上面的代码将找到obj.a.b.etc给定的obj和字符串< cc>。)

为了回应那些仍然害怕使用"a.b.etc"的人,尽管它在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编译器所做的优化,您可能需要确保没有通过常规方法在每次调用时都重新定义任何嵌套函数(将它们放在闭包,对象或全局名称空间中)。

编辑:

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


  您如何将其转换为二传手?不仅通过路径返回值,而且如果将新值发送到函数中,还要设置它们? –斯瓦德6月28日21:42


(旁注:可悲的是无法返回带有Setter的对象,因为这将违反调用约定;注释者似乎改为引用具有诸如reduceindex(obj,"a.b.etc", value)这样的副作用的通用setter样式的函数。)

obj.a.b.etc = value样式确实不适合此样式,但是我们可以修改递归实现:

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


...虽然我个人还是建议创建一个单独的函数reduce。最后,我想补充一下,问题的原始提出者可以(应该?)使用索引数组(可以从setIndex(...)获得),而不是字符串。尽管便利功能通常没有错。



有评论者问:


  数组呢?像“ a.b [4] .c.d [1] [2] [3]”之类的东西? –AlexS


Javascript是一种很奇怪的语言。通常,对象只能将字符串作为其属性键,因此,例如,如果.split是像x这样的通用对象,则x={}会变成x[1] ...您没看错...是的。 。

Java数组(它们本身是Object的实例)特别鼓励使用整数键,即使您可以执行x["1"]之类的操作。

但通常(且有例外)x=[]; x["puppy"]=5;(在允许的情况下;您不能执行x["somestring"]===x.somestring)。

(请记住,如果使用的JS编译器可以证明不会违反规范,则可能会选择将其编译为更精巧的表示形式。)

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

这将等效于["stringindex"],尽管我们可能还应该支持a["b"][4]["c"]["d"][1][2][3]。您必须检查ecmascript grammar section on string literals以查看如何解析有效的字符串文字。从技术上讲,您还想检查(不同于我的第一个答案)a.b["c\"validjsstringliteral"][3]是有效的javascript identifier

但是,如果您的字符串中不包含逗号或方括号,那么一个简单的答案就是匹配长度为1+的字符序列(不在集合a,[中):

> "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编辑:

让我们全力以赴,为语法纯净性做一个最有效,最可怕的,超编程的解决方案,我们可以提出...。使用ES6代理对象!...我们还要定义一些属性(imho很好,但是很不错),它们可能会破坏编写不正确的库。如果您关心表现,理智(您或他人的),工作等,则可能应该谨慎使用此功能。

// [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}}}
  
  (代理覆盖获取)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之类的输入。



根据建议,也许您想以一种``更软''的NaN风格处理未定义的索引(例如keys = ['a','b','c']; obj.H[keys]返回未定义而不是未捕获的TypeError)...:

1)从一维索引情况({})['eg'] == undefined中“应该返回未定义而不是抛出错误”的角度来看,这是有意义的,因此“我们应该返回未定义而不是抛出错误。错误”。

2)从我们正在做的index({a:{b:{c:...}}}, 'a.x.c')的角度来看,这是没有意义的,在上面的示例中,它会因TypeError而失败。

就是说,您可以通过以下任一方法来替换约简函数,从而完成这项工作:

x['a']['x']['c'],或
(o,i)=>o===undefined?undefined:o[i]

(您可以通过使用for循环并在未定义要进入下一个索引的子结果时中断/返回来提高效率,或者如果您希望这种故障很少发生,则可以使用try-catch来提高效率。)

关于javascript - 将点表示法的JavaScript字符串转换为对象引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45948616/

相关文章:

javascript - 如何在 ReactJS 中使列表中的项目可点击

javascript - 如何在 Directus 中创建报告页面

.net - 在浏览器中使用 View 时,CSS 未在 ASP.NET 站点中加载

javascript - 使用方法 : :delete 在 RAILS 中删除

javascript - 如何将 CSS 仅应用于我的自定义 WordPress 简码?

javascript - 从 javascript 客户端调用 javascript 适配器会收到 415 Unsupported Media Type 响应

javascript - JQuery 如何在使用 Moment JS 时返回正确的日期

javascript - 在将 blob 提交到服务器之前等待 canvas.toDataURL() 完成

javascript - s3.getObject(...).createReadStream 不是函数

javascript - 是否有一个 JavaScript 等价于 Python pass 语句,它什么都不做?