将点符号中的JavaScript字符串转换为对象引用

时间:2011-06-18 04:44:09

标签: javascript

给定一个JS对象

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

和一个字符串

"a.b"

如何将字符串转换为点符号,以便我可以去

var val = obj.a.b

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

30 个答案:

答案 0 :(得分:352)

答案 1 :(得分:47)

如果你可以使用lodash,那么就有一个功能就是这样:

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

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

答案 2 :(得分:16)

一个更复杂的递归示例。

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

答案 3 :(得分:8)

如果您希望多次取消引用相同的路径,为每个点表示法路径构建一个函数实际上具有best performance by far(在上面的注释中与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");

答案 4 :(得分:7)

自原帖以来已有多年。 现在有一个很棒的图书馆名为' 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");

适用于深层嵌套的属性和数组。

答案 5 :(得分:7)

你也可以使用lodash.get

您只需安装此软件包(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;

答案 6 :(得分:5)

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

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

答案 7 :(得分:4)

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所说,you should never use eval

另外,JSFiddle

答案 8 :(得分:4)

我已经通过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/

答案 9 :(得分:4)

请注意,如果您已使用Lodash,则可以使用propertyget功能:

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

下划线还具有property功能,但它不支持点表示法。

答案 10 :(得分:3)

我建议拆分路径并迭代它并减少你拥有的对象。对于缺少的属性,此提案的默认值为

function getValue(object, keys) {
    return keys.split('.').reduce(function (o, k) {
        return (o || {})[k];
    }, object);
}

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

答案 11 :(得分:3)

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

答案 12 :(得分:2)

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

答案 13 :(得分:2)

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

答案 14 :(得分:2)

您可以通过点符号获取对象成员的值,只需一行代码:

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

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

希望它有所帮助。

答案 15 :(得分:1)

如果您想将字符串点符号转换为对象,我已经制作了一个方便的小帮手,它可以将像 a.b.c.d 这样的字符串转换为 {{1} } e 返回:

dotPathToObject("a.b.c.d", "value")

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

答案 16 :(得分:1)

我从Ricardo Tomasi's answer复制了以下内容并进行了修改,以便根据需要创建不存在的子对象。效率稍低(更多if s并创建空对象),但应该相当不错。

此外,它允许我们执行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]
 }

答案 17 :(得分:0)

这是我提出的扩展解决方案: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 byIndex method
    return path.split('.').reduce(byIndex, obj)
}

答案 18 :(得分:0)

冒着击败死马的风险...... 我发现这在遍历嵌套对象时最有用,可以引用相对于基础对象或具有相同结构的类似对象的位置。为此,这对嵌套对象遍历函数很有用。请注意,我使用了一个数组来保存路径。修改它以使用字符串路径或数组将是微不足道的。另请注意,您可以为该值指定“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);
}

答案 19 :(得分:0)

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

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

答案 20 :(得分:0)

这是我的实施

实施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 (使用数组缩减代替切片)

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'

答案 21 :(得分:0)

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


这将转换类似

ffmpeg -i input-hev1.mp4 -c:v copy -tag:v hvc1 -bsf:v hevc_mp4toannexb
-c:a copy output-hvc1.mov

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

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

答案 22 :(得分:0)

是的,它在4年前被问到是的,扩展基础原型通常不是一个好主意,但是,如果你将所有扩展保存在一个地方,它们可能会有用。
所以,这是我的方法。

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

现在,您可以在任何地方获取嵌套属性,而无需使用函数或复制/粘贴功能导入模块。

UPD.Example:

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

UPD 2.下一个扩展程序在我的项目中破坏了mongoose。我也读过它可能会破坏jquery。所以,永远不要以下一个方式去做

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

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

    return result;
};

答案 23 :(得分:0)

使用object-scan似乎有点矫kill过正,但是您可以轻松做到

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

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

答案 24 :(得分:0)

如果您想以最快的方式执行此操作,同时处理路径解析或属性解析的任何问题,请查看 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

答案 25 :(得分:0)

这是我的代码,不使用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

答案 26 :(得分:0)

这是其中一种情况,您询问 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, "a.b", "3");
setFluently(origObj, "a.c", "4");

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

答案 27 :(得分:0)

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

答案 28 :(得分:-1)

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

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

答案 29 :(得分:-1)

这是我要付的10美分,波纹管功能将根据提供的路径进行获取/设置。 确保您可以改善它,删除||如果您确实错误地考虑了错误的值,则将其替换为Object.hasOwnProperty

我用a.b.c和a.b.2.c {a:{b:[0,1,{c:7}]}}对其进行了测试,并且可以同时进行设置和获取:)。

cheerz

function helper(obj, path, setValue){
  const l = String(path).split('.');
  return l.reduce((o,i, idx)=>{
   if( l.length-idx===1)  { o[i] = setValue || o[i];return setValue ? obj : o[i];}
  o[i] = o[i] || {};
   return o[i];
  }, x)
}