将点符号中的JavaScript字符串转换为对象引用 [英] Convert JavaScript string in dot notation into an object reference

查看:87
本文介绍了将点符号中的JavaScript字符串转换为对象引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给定一个JS对象: var obj = {a:{b:'1',c:'2'}} 和一个字符串 ab如何将字符串转换为点符号,以便我可以: var val = obj.ab ;

Given a JS Object: var obj = { a: { b: '1', c: '2' } } and a string "a.b" how can I convert the string to dot notation so I can go: var val = obj.a.b;

如果字符串只是'a',我可以使用 obj [a] ,但这更复杂。我想有一些直截了当的方法,但它目前无法逃脱。

If the string was just 'a' I can use obj[a] but this is more complex. I imagine there is some straightforward method but it escapes at present.

推荐答案


最近的笔记:虽然我很高兴这个答案得到了很多赞成,但我也有点害怕。如果需要将诸如x.a.b.c之类的点符号字符串转换为引用,则可能表明存在一些非常错误(除非您执行某些奇怪的反序列化)。它是矫枉过正,因为它是不必要的元编程,也有点违反功能性无副作用编码风格。此外,如果您执行的操作超出了您的需要(例如,作为您的应用程序传递对象并取消引用它们的默认形式),也可以获得大量性能命中。如果由于某种原因这是服务器端的js,通常用于输入的清理。找到回答这个问题的新手应该考虑使用数组表示,例如['x','a','b','c'],或者甚至是更直接/简单/直接的东西,如果可能的话,比如不会丢失对引用本身的跟踪,或者可能是某些预先存在的唯一ID等。

recent note: While I'm flattered that this answer has gotten many upvotes, I am also somewhat horrified. If one needs to convert dot-notation strings like "x.a.b.c" into references, it's probably a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization). It is overkill because it is unnecessary metaprogramming, and also somewhat violates functional side-effect-free coding style. Also, expect massive performance hits as well if you do this more than you need to (e.g. as your app's default form of passing objects around and dereferencing them). If for some reason this is server-side js, the usual holds for sanitization of inputs. Novices who find their way to this answer should consider working with array representations instead, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible, like not losing track of the references themselves, or maybe some pre-existing unique id, etc.

这是一款优雅的单线程,比其他解决方案短10倍:

Here's an elegant one-liner that's 10x shorter than the other solutions:

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

[edit]或在ECMAScript 6中:

[edit] Or in ECMAScript 6:

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

(并不是说我认为eval总是像其他人一样暗示它(尽管通常是这样),但是那些人会很高兴这种方法不使用eval 。上面会找到 obj.abetc 给定 obj 和字符串abetc 。)

(Not that I think eval always bad like others suggest it is (though it usually is), nevertheless those people will be pleased that this method doesn't use eval. The above will find obj.a.b.etc given obj and the string "a.b.etc".)

回应那些仍然害怕使用减少的人,尽管它参加ECMA-262标准(第5版),这是一个两行递归实现:

In response to those who still are afraid of using reduce despite it being in the ECMA-262 standard (5th edition), here is a two-line recursive implementation:

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编译器正在进行的优化,您可能需要确保任何嵌套函数不会通过常规方法在每次调用中重新定义(将它们放在闭包,对象或全局命名空间中)。

Depending on the optimizations the JS compiler is doing, you may want to make sure any nested functions are not re-defined on every call via the usual methods (placing them in a closure, object, or global namespace).

编辑

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

To answer an interesting question in the comments:


你会怎么转这也成了一个二传手?不仅要按路径返回值,还要在将新值发送到函数时设置它们? - Swader Jun 28 21:42

how would you turn this into a setter as well? Not only returning the values by path, but also setting them if a new value is sent into the function? – Swader Jun 28 at 21:42

(旁注:遗憾的是不能用Setter返回一个对象,因为这会违反调用约定; commenter似乎是指一般的setter样式函数,其副作用如 index(obj,abetc,value)执行 obj.abetc = value 。)

(sidenote: sadly can't return an object with a Setter, as that would violate the calling convention; commenter seems to instead be referring to a general setter-style function with side-effects like index(obj,"a.b.etc", value) doing obj.a.b.etc = value.)

reduce 样式不是非常适合,但我们可以修改递归实现:

The reduce style is not really suitable to that, but we can modify the recursive implementation:

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

...虽然我亲自d建议创建一个单独的函数 setIndex(...)。我想以旁注方式结束,问题的原始装腔作者可以(应该?)使用索引数组(他们可以从 .split 获得)而不是字符串;虽然便利功能通常没有任何问题。

...though personally I'd recommend making a separate function setIndex(...). I would like to end on a side-note that the original poser of the question could (should?) be working with arrays of indices (which they can get from .split), rather than strings; though there's usually nothing wrong with a convenience function.

一位意见提供者问:


数组怎么样?像a.b [4] .c.d [1] [2] [3]这样的东西? -AlexS

what about arrays? something like "a.b[4].c.d[1][2][3]" ? –AlexS

Javascript是一种非常奇怪的语言;通常,对象只能将字符串作为其属性键,例如,如果 x 是一个通用对象,如 x = {} ,然后 x [1] 将变为 x [1] ...你读得对。 ..是...

Javascript is a very weird language; in general objects can only have strings as their property keys, so for example if x was a generic object like x={}, then x[1] would become x["1"]... you read that right... yup...

Javascript Arrays(它们本身就是Object的实例)特别鼓励整数键,即使你可以做类似 x的事情= []; x [puppy] = 5;

Javascript Arrays (which are themselves instances of Object) specifically encourage integer keys, even though you could do something like x=[]; x["puppy"]=5;.

但一般情况下(也有例外), x [ somestring] === x.somestring (当它被允许时;你不能 x.123 )。

But in general (and there are exceptions), x["somestring"]===x.somestring (when it's allowed; you can't do x.123).

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

(Keep in mind that whatever JS compiler you're using might choose, maybe, to compile these down to saner representations if it can prove it would not violate the spec.)

因此,您的问题的答案取决于您是否假设这些对象仅接受整数(由于您的问题域中的限制),或者不是。我们假设没有。然后一个有效的表达式是一个基本标识符加上一些 .identifier s和一些 [stringindex] s的串联

So the answer to your question would depend on whether you're assuming those objects only accept integers (due to a restriction in your problem domain), or not. Let's assume not. Then a valid expression is a concatenation of a base identifier plus some .identifiers plus some ["stringindex"]s

这相当于 a [b] [4] [c] [d] [1] [ 2] [3] ,虽然我们应该也支持 ab [c\validjsstringliteral] [3] 。你有检查有关字符串文字的 ecmascript语法部分看看如何解析有效的字符串文字。从技术上讲,你还要检查(与我的第一个答案不同) a 是有效的 javascript标识符

This would then be equivalent to a["b"][4]["c"]["d"][1][2][3], though we should probably also support a.b["c\"validjsstringliteral"][3]. You'd have to check the ecmascript grammar section on string literals to see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that a is a valid javascript identifier.

简单回答你的问题,如果你的字符串不包含逗号或括号,那就是匹配长度不超过集合 []

A simple answer to your question though, if your strings don't contain commas or brackets, would be just be to match length 1+ sequences of characters not in the set , or [ or ]:

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

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

If your strings don't contain escape characters or " characters, and because IdentifierNames are a sublanguage of StringLiterals (I think???) you could first convert your dots to []:

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

当然,要小心,永远不要相信你的数据。执行此操作的一些不好的方法可能适用于某些用例还包括:

Of course, always be careful and never trust your data. Some bad ways to do this that might work for some use cases also include:

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


Special 2018 edit:

让我们走完整圈,做一个我们能想出的最低效,可怕的过度编程的解决方案......为了语法 pure hamfistery的利益。使用ES6代理对象!...让我们定义一些属性(imho很好但很棒)但可能会破坏不正确编写的库。如果你关心表现,理智(你或他人),你的工作等,你或许应该警惕使用它。

Let's go full-circle and do the most inefficient, horribly-overmetaprogrammed solution we can come up with... in the interest of syntactical purityhamfistery. With ES6 Proxy objects!... Let's also define some properties which (imho are fine and wonderful but) may break improperly-written libraries. You should perhaps be wary of using this if you care about performance, sanity (yours or others'), your job, etc.

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

obj is: {"a":{"b":{"c":1,"d":2}}}

(代理覆盖获取)objHyper ['abc']是:1

(proxy override get) objHyper['a.b.c'] is: 1

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

(proxy override set) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}}

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

(behind the scenes) objHyper is: Proxy {a: {…}}

(快捷方式)obj.H ['abc'] = 4

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

(快捷方式)obj。 H ['abc']是obj ['a'] ['b'] ['c']是:4

(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4

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

inefficient idea: You can modify the above to dispatch based on the input argument; either use the .match(/[^\]\[.]+/g) method to support obj['keys'].like[3]['this'], or if instanceof Array, then just accept an Array as input like keys = ['a','b','c']; obj.H[keys].

建议您可能要处理未定义的索引以'更柔和'的NaN风格方式(例如索引({a:{b:{c:...}}},'axc')返回undefined而不是比未被捕获的TypeError)...:

Per suggestion that maybe you want to handle undefined indices in a 'softer' NaN-style manner (e.g. index({a:{b:{c:...}}}, 'a.x.c') return undefined rather than uncaught TypeError)...:

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

1) This makes sense from the perspective of "we should return undefined rather than throw an error" in the 1-dimensional index situation ({})['e.g.']==undefined, so "we should return undefined rather than throw an error" in the N-dimensional situation.

2)这个我们正在做的 x ['a'] ['x'] ['c'] 的观点 是否有意义在上面的示例中出现了TypeError失败。

2) This does not make sense from the perspective that we are doing x['a']['x']['c'], which would fail with a TypeError in the above example.

这就是说,你可以通过用以下任一方式替换你的还原函数来实现这个目的:

That said, you'd make this work by replacing your reducing function with either:

(o,i)=> o === undefined?undefined:o [i] ,或
(o,i)=>(o || {})[i]

(你可以通过以下方式提高效率我们每当您下一次索引的子结构未定义时,或者使用try-catch,如果您认为这些失败的事情是非常罕见的,那么可以使用for循环和中断/返回。)

(You can make this more efficient by using a for loop and breaking/returning whenever the subresult you'd next index into is undefined, or using a try-catch if you expect things such failures to be sufficiently rare.)

这篇关于将点符号中的JavaScript字符串转换为对象引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆