繁体   English   中英

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

[英]Convert a JavaScript string in dot notation into an object reference

给定一个 JavaScript 对象,

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

和一个字符串

"a.b"

我怎样才能将字符串转换为点符号,这样我就可以去

var val = obj.a.b

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

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

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

如果您的用例很小并且您不会遇到性能问题,那么通常这样做通常是可以的,并且您不需要建立在您的抽象之上以使其稍后变得更加复杂。 事实上,如果这会降低代码复杂性并让事情变得简单,那么您可能应该继续做 OP 要求的事情。 但是,如果不是这种情况,请考虑以下是否适用:

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

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

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

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

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

这是一个优雅的单线,比其他解决方案短 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.abetc给定obj和字符串"abetc" .)

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

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 的对象,因为这会违反调用约定;评论者似乎指的是具有副作用的通用 setter 样式函数,例如index(obj,"abetc", value)obj.abetc = 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获得),而不是字符串; 尽管便利功能通常没有问题。


一位评论者问道:

数组呢? 像“ab[4].cd[1][2][3]”这样的东西? –亚历克斯

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

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

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

(请记住,您使用的任何 JS 编译器都可能会选择,如果可以证明它不会违反规范,则将它们编译为更合理的表示。)

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

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

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

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

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

(proxy override get) objHyper['abc'] is: 1

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

(幕后)objHyper 是:Proxy {a: {…}}

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

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

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


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

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

  2. 从我们正在执行x['a']['x']['c']的角度来看,这没有任何意义,在上面的示例中这将失败并出现 TypeError。

也就是说,您可以通过以下任一方式替换您的归约函数来完成这项工作:

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

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

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

_.get(对象,路径,[defaultValue])

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

你可以使用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';

一个更复杂的递归示例。

 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, 'adab')); //blah

2021

每次您希望程序中的新功能时,您都不需要引入另一个依赖项。 现代 JS 非常有能力和可选链接运算符?. 现在得到了广泛的支持,并使这种任务变得非常简单。

只需一行代码,我们就可以编写get输入对象t和字符串path的 get 。 它适用于任何嵌套级别的对象数组 -

 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, "ab1.cd0")) console.log(get(mydata, "ab1.cd1")) console.log(get(mydata, "abxyz"))

"hello"
"world"
undefined

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

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

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

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

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

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

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

自从原来的帖子很多年了。 现在有一个很棒的库,叫做“object-path”。 https://github.com/mariocasciaro/object-path

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

这很简单:

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

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

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

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

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

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

Underscore.js也有property功能,但不支持点符号。

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

通过 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 语句的字符串。

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

希望能帮助到你。

GET / SET答案也适用于本机反应(您当前无法分配给Object.prototype ):

Object.defineProperty(Object.prototype, 'getNestedProp', {
    value: function(desc) {
        var obj = this;
        var arr = desc.split(".");
        while(arr.length && (obj = obj[arr.shift()]));
        return obj;
    },
    enumerable: false
});

Object.defineProperty(Object.prototype, 'setNestedProp', {
    value: function(desc, value) {
        var obj = this;
        var arr = desc.split(".");
        var last = arr.pop();
        while(arr.length && (obj = obj[arr.shift()]));
        obj[last] = value;
    },
    enumerable: false
});

用法:

var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };

a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo

使用 Array Reduce 函数将根据提供的路径获取/设置。

我用abc和 ab2.c {a:{b:[0,1,{c:7}]}}对其进行了测试,它适用于获取键或变异对象以设置值

欢呼

 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).ac===22 || 'Test Case: Direct set failed' ) console.log( setOrGet({a: {b:[1,2]}},'ab1',22).ab[1]===22 || 'Test Case: Direct set on array failed' ) console.log( setOrGet({a: {b:{c: {e:1} }}},'abce',22).abc e===22 || 'Test Case: deep get failed' ) // failed !. Thats your homework :) console.log( setOrGet({a: {b:{c: {e:[1,2,3,4,5]} }}},'abce3 ',22) )

我个人的建议。

除非没有其他办法,否则不要使用这样的东西!

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

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

我从里卡多·托马西的回答中复制了以下内容,并进行了修改以根据需要创建尚不存在的子对象。 它的效率有点低(更多if和创建空对象),但应该相当不错。

此外,它还允许我们在以前做不到的地方做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]
 }

几年后,我发现它可以处理范围和数组。 例如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

如果您希望将任何包含点符号键的对象转换为这些键的数组版本,您可以使用它。


这将转换为

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

}

如果您想将字符串点表示法转换为object ,我做了一个方便的小帮手,它可以使用dotPathToObject("abcd", "value")返回此值来转换像abcd这样的值为e的字符串:

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

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

解决方案:

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) 

这是我不使用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

是的,扩展基本原型通常不是一个好主意,但是,如果您将所有扩展都放在一个地方,它们可能会很有用。 所以,这是我的方法。

   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扩展在我的项目中破坏了Mongoose 我也读过它可能会破坏 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而不是切片)

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'

这是ninjagecko 提出的我的扩展解决方案。

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

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

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

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

使用对象扫描似乎有点矫枉过正,但你可以简单地做

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

免责声明:我是对象扫描的作者

自述文件中有很多更高级的示例。

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

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

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

示例使用:

<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, "ab", "3"); setFluently(origObj, "ac", "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 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
    }
  }
}

冒着打死马的风险......我发现这在遍历嵌套对象以参考您相对于基础对象或具有相同结构的类似对象的位置时最有用。 为此,这对于嵌套对象遍历函数很有用。 请注意,我使用了一个数组来保存路径。 修改它以使用字符串路径或数组将是微不足道的。 另请注意,与其他一些实现不同,您可以将“未定义”分配给该值。

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

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

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]

如果您想以最快的方式执行此操作,同时处理路径解析或属性解析的任何问题,请查看path-value

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

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

该库是 100% TypeScript,适用于 NodeJS + 所有网络浏览器。 它是完全可扩展的,您可以使用较低级别的resolvePath ,并根据需要以自己的方式处理错误。

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

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

想把这个放在那里:

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% 安全的(尽管我希望看到建议可能绕过的评论)也不是有效的,但它可能适用于路径的灵活性(可选链接等)以及一些缓解措施是需要。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM