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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6393943/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 21:32:01  来源:igfitidea点击:

Convert JavaScript string in dot notation into an object reference

javascript

提问by nevf

Given a JS object

给定一个 JS 对象

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

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

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

回答by ninjagecko

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 could (maybe) be a sign that there is something very wrong going on (unless maybe you're performing some strange deserialization).

That is to say, novices who find their way to this answer must ask themselves the question "why am I doing this?"

case 1: As the primary method of working with your data (e.g. as your app's default form of passing objects around and dereferencing them). Like asking "how can I look up a function or variable name from a string".

  • This is bad programming practice (unnecessary metaprogramming specifically, and kind of violates function side-effect-free coding style, and will have performance hits). Novices who find themselves in this case, should instead consider working with array representations, e.g. ['x','a','b','c'], or even something more direct/simple/straightforward if possible: like not losing track of the references themselves in the first place (most ideal if it's only client-side or only server-side), etc. (A pre-existing unique id would be inelegant to add, but could be used if the spec otherwise requires its existence regardless.)

case 2: Working with serialized data, or data that will be displayed to the user. Like using a date as a string "1999-12-30" rather than a Date object (which can cause timezone bugs or added serialization complexity if not careful).

  • This is maybe fine. Be careful that there are no dot strings "." in your sanitized input fragments.

If you find yourself using this answer all the time and converting back and forth between string and array, you may be in the bad case, and should consider an alternative.

最近的笔记:虽然我很高兴这个答案得到了很多赞成,但我也有点害怕。如果需要将诸如“xabc”之类的点符号字符串转换为引用,这可能(可能)表明发生了一些非常错误的事情(除非您可能正在执行一些奇怪的反序列化)。

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

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

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

情况 2:使用序列化数据或将显示给用户的数据。就像使用日期作为字符串“1999-12-30”而不是日期对象(如果不小心,可能会导致时区错误或增加序列化复杂性)。

  • 这也许没问题。请注意没有点字符串“。” 在您清理过的输入片段中。

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

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

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

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

[edit] Or in ECMAScript 6:

[编辑] 或者在 ECMAScript 6 中:

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

(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.etcgiven objand the string "a.b.etc".)

(并不是说我认为 eval 总是像其他人所说的那样不好(尽管通常如此),但是这些人会很高兴此方法不使用 eval。上面会找到obj.a.b.etcgivenobj和 string "a.b.etc"。)

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

为了回应那些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')

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

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

edit:

编辑

To answer an interesting question in the comments:

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

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

你如何把它变成一个二传手?不仅按路径返回值,而且在将新值发送到函数时设置它们?– 斯瓦德 6 月 28 日 21:42

(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.)

(旁注:遗憾的是不能用 Setter 返回一个对象,因为这会违反调用约定;评论者似乎指的是一个具有副作用的通用 setter 风格的函数,比如index(obj,"a.b.etc", value)do obj.a.b.etc = value。)

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

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

Demo:

演示:

> 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

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

...虽然我个人建议制作一个单独的功能setIndex(...)。我想以旁注结束,问题的原始提出者可以(应该?)使用索引数组(他们可以从中获取.split),而不是字符串;尽管便利功能通常没有问题。



A commenter asked:

一位评论者问道:

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

数组呢?类似于“ab[4].cd[1][2][3]”?——亚历克斯

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

Javascript 是一种非常奇怪的语言;一般来说,对象只能将字符串作为它们的属性键,所以例如,如果x是一个像 一样的通用对象x={},那么x[1]就会变成x["1"]……你没看错……是的……

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

Javascript 数组(它们本身就是 Object 的实例)特别鼓励使用整数键,即使您可以执行类似x=[]; x["puppy"]=5;.

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

但总的来说(也有例外),x["somestring"]===x.somestring(当它被允许时;你不能这样做x.123)。

(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.)

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

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

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

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 literalsto see how to parse a valid string literal. Technically you'd also want to check (unlike in my first answer) that ais a valid javascript identifier.

这将等同于a["b"][4]["c"]["d"][1][2][3],尽管我们可能也应该支持a.b["c\"validjsstringliteral"][3]. 您必须检查有关字符串文字ecmascript 语法部分,以了解如何解析有效的字符串文字。从技术上讲,您还想检查(与我的第一个答案不同)这a是一个有效的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 ]:

一个简单的回答你的问题,虽然,如果你的字符串不包含逗号或支架,将只是以不相匹配的字符集的长度1+序列,[]

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

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 []:

如果您的字符串不包含转义字符或"字符,并且因为 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"]

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


Special 2018 edit:

2018年特别编辑:

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.

为了句法纯度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);
}

Demo:

演示:

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

Output:

输出:

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

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

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

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

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

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

对象是:{"a":{"b":{"c":1,"d":2}}}

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

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

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

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

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

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

低效的想法:你可以修改上面的基于输入参数的dispatch;要么使用该.match(/[^\]\[.]+/g)方法来支持obj['keys'].like[3]['this'],要么如果instanceof Array,则只接受一个数组作为输入,例如keys = ['a','b','c']; obj.H[keys]



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)...:

根据建议,也许您想以“更软”的 NaN 样式方式处理未定义的索引(例如,index({a:{b:{c:...}}}, 'a.x.c')返回未定义而不是未捕获的 TypeError)...:

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.

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

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

2)这并没有从这个角度说,我们正在做的意义x['a']['x']['c'],这将失败,并在上面的例子中一个类型错误。

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

也就是说,您可以通过将您的减少功能替换为:

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

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

(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.)

(您可以通过使用 for 循环并在您下一个索引的子结果未定义时中断/返回来提高效率,或者如果您希望此类失败非常罕见,则使用 try-catch。)

回答by Petar Ivanov

If you can use lodash, there is a function, which does exactly that:

如果您可以使用lodash,则有一个函数可以做到这一点:

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

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

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

回答by joekarl

A little more involved example with recursion.

一个更复杂的递归示例。

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

alert(recompose(obj,'a.d.a.b')); //blah

回答by DarkCrazy

you could also use lodash.get

你也可以使用lodash.get

You just install this package (npm i --save lodash.get) and then use it like this:

你只需安装这个包(npm i --save lodash.get)然后像这样使用它:

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

const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };

console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints  123

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

回答by Kevin Crumley

If you expect to dereference the same path many times, building a function for each dot notation path actually has the best performance by far(expanding on the perf tests James Wilkins linked to in comments above).

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

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

Using the Function constructor has some of the same drawbacks as eval() in terms of security and worst-case performance, but IMO it's a badly underused tool for cases where you need a combination of extreme dynamism and high performance. I use this methodology to build array filter functions and call them inside an AngularJS digest loop. My profiles consistently show the array.filter() step taking less than 1ms to dereference and filter about 2000 complex objects, using dynamically-defined paths 3-4 levels deep.

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

A similar methodology could be used to create setter functions, of course:

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

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

回答by LPG

Many years since the original post. Now there is a great library called 'object-path'. https://github.com/mariocasciaro/object-path

距离原帖已经很多年了。现在有一个很棒的库,叫做“object-path”。 https://github.com/mariocasciaro/object-path

Available on NPM and BOWER https://www.npmjs.com/package/object-path

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

It's as easy as:

这很简单:

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

And works for deeply nested properties and arrays.

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

回答by Nina Scholz

I suggest to split the path and iterate it and reduce the object you have. This proposal works with a default valuefor missing properties.

我建议拆分路径并对其进行迭代并减少您拥有的对象。此提案使用缺失属性的默认值

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

回答by Tamlyn

Note if you're already using Lodashyou can use the propertyor getfunctions:

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

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

Underscore also has a propertyfunction but it doesn't support dot notation.

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

回答by Ricardo Tomasi

Other proposals are a little cryptic, so I thought I'd contribute:

其他建议有点神秘,所以我想我会做出贡献:

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

回答by Jeremias Santos

You can use the library available at npm, which simplifies this process. https://www.npmjs.com/package/dot-object

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