通过字符串路径访问嵌套JavaScript对象和数组

通过字符串路径访问嵌套JavaScript对象和数组,javascript,path,nested,Javascript,Path,Nested,我的数据结构如下: var someObject = { 'part1' : { 'name': 'Part 1', 'size': '20', 'qty' : '50' }, 'part2' : { 'name': 'Part 2', 'size': '15', 'qty' : '60' }, 'part3' : [ {

我的数据结构如下:

var someObject = {
    'part1' : {
        'name': 'Part 1',
        'size': '20',
        'qty' : '50'
    },
    'part2' : {
        'name': 'Part 2',
        'size': '15',
        'qty' : '60'
    },
    'part3' : [
        {
            'name': 'Part 3A',
            'size': '10',
            'qty' : '20'
        }, {
            'name': 'Part 3B',
            'size': '5',
            'qty' : '20'
        }, {
            'name': 'Part 3C',
            'size': '7.5',
            'qty' : '20'
        }
    ]
};
我想使用以下变量访问数据:

var part1name = "part1.name";
var part2quantity = "part2.qty";
var part3name1 = "part3[0].name";
part1name应该用
someObject.part1.name
的值填充,即“part1”。零件2的数量与填充60的数量相同


无论是纯javascript还是JQuery,都可以实现这一点吗?

我想你是在问这个问题:

var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 =  someObject.part3[0].name;
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 =  someObject["part3"][0]["name"];
你可能会问:

var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 =  someObject.part3[0].name;
var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 =  someObject["part3"][0]["name"];
这两种方法都有效


或者你是在问这个

var partName = "part1";
var nameStr = "name";

var part1name = someObject[partName][nameStr];
var partName = "part1.name";

var partBits = partName.split(".");

var part1name = someObject[partBits[0]][partBits[1]];

最后你可能会问这个

var partName = "part1";
var nameStr = "name";

var part1name = someObject[partName][nameStr];
var partName = "part1.name";

var partBits = partName.split(".");

var part1name = someObject[partBits[0]][partBits[1]];

如果在编码时需要访问不同的嵌套键而不知道它(寻址它们很简单),可以使用数组表示法访问器:

var part1name = someObject['part1']['name'];
var part2quantity = someObject['part2']['qty'];
var part3name1 =  someObject['part3'][0]['name'];
它们相当于点符号访问器,在运行时可能会有所不同,例如:

var part = 'part1';
var property = 'name';

var part1name = someObject[part][property];
'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
相当于

var part1name = someObject['part1']['name'];

我希望这能解决你的问题

编辑

我不会使用字符串来保存某种xpath查询以访问对象值。 由于必须调用函数来解析查询并检索值,因此我将遵循另一条路径(不是:

或者,如果您对应用方法感到不安

函数更短、更清晰,解释器会为您检查语法错误等


顺便说一句,我觉得在合适的时间做一个简单的赋值就足够了…

您必须自己解析字符串:

function getProperty(obj, prop) {
    var parts = prop.split('.');

    if (Array.isArray(parts)) {
        var last = parts.pop(),
        l = parts.length,
        i = 1,
        current = parts[0];

        while((obj = obj[current]) && i < l) {
            current = parts[i];
            i++;
        }

        if(obj) {
            return obj[last];
        }
    } else {
        throw 'parts is not valid array';
    }
}
它使解析更容易


我只是根据我已经拥有的一些类似代码做了这个,它似乎可以工作:

Object.byString = function(o, s) {
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');
    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
        } else {
            return;
        }
    }
    return o;
}
在上查看工作演示

EDIT一些人注意到,如果传递的字符串中最左边的索引与对象中正确嵌套的条目不对应,则此代码将抛出错误。这是一个值得关注的问题,但最好在调用时使用
try/catch
块,而不是让此函数以静默方式返回
un为无效索引定义了

使用eval:

var part1name = eval("someObject.part1.name");
wrap在出现错误时返回undefined

function path(obj, path) {
    try {
        return eval("obj." + path);
    } catch(e) {
        return undefined;
    }
}


使用eval时请使用常识和谨慎。它有点像光剑,如果你打开它,有90%的几率你会切断一条肢体。这不适合所有人。

也适用于对象内部的阵列/阵列。 防御无效值

/**
*从对象/数组检索嵌套项
*@param{Object|Array}obj
*@param{String}路径点分隔
*@param{*}def默认值(如果结果未定义)
*@returns{*}
*/
功能路径(obj、路径、def){
变量i,len;
对于(i=0,path=path.split('.'),len=path.length;i

最近遇到了同样的问题,并且成功地使用了
设置
嵌套对象/数组:

获取:

设置:


这个解决方案怎么样:

setJsonValue: function (json, field, val) {
  if (field !== undefined){
    try {
      eval("json." + field + " = val");
    }
    catch(e){
      ;
    }
  }  
}
还有这个,为了得到:

getJsonValue: function (json, field){
  var value = undefined;
  if (field !== undefined) {
    try {
      eval("value = json." + field);
    } 
    catch(e){
      ;
    }
  }
  return value;
};

可能有些人认为它们不安全,但是它们必须快得多,解析字符串。

< p>这是我使用的解决方案:

function resolve(path, obj=self, separator='.') {
    var properties = Array.isArray(path) ? path : path.split(separator)
    return properties.reduce((prev, curr) => prev && prev[curr], obj)
}
用法示例:

// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)

// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'

// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})

// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42

// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');
限制:

  • 无法使用括号(
    []
    )作为数组索引,尽管在分隔符标记(例如,
    )之间指定数组索引可以正常工作,如上所示

现在有一个
npm
模块用于执行此操作:

用法示例:

// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)

// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'

// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})

// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42

// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42
var access = require('safe-access');
access(very, 'nested.property.and.array[0]');

在这里,我提供了更多的方法,在许多方面似乎更快:

选项1:拆分字符串。或[或]或“或”,将其反转,跳过空项

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
    while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
    return origin;
}
选项2(最快的,除了
eval
):低级字符扫描(没有regex/split/etc,只是快速字符扫描)。 注意:这个不支持索引的引号

函数getValue(路径、原点){ 如果(原点===void 0 | |原点===null)原点=self?self:此; 如果(路径类型!=='string')路径=''+路径; var c='',pc,i=0,n=path.length,name=''; if(n)while(i) Speigg的方法非常简洁明了,尽管我在搜索通过字符串路径访问AngularJS$scope属性的解决方案时找到了这个答案,只需稍加修改,它就完成了这项工作:

$scope.resolve = function( path, obj ) {
    return path.split('.').reduce( function( prev, curr ) {
        return prev[curr];
    }, obj || this );
}
只需将此函数放在根控制器中,并在任何子作用域中使用它,如下所示:

$scope.resolve( 'path.to.any.object.in.scope')

您可以通过以下简单技巧,在不使用任何外部JavaScript库的情况下,使用点表示法获取deep对象成员的值:

new Function('_', 'return _.' + path)(obj);
在您的情况下,要从
someObject
获取
part1.name
的值,只需执行以下操作:

new Function('_', 'return _.part1.name')(someObject);

这里是一个简单的fiddle演示:

我还没有找到一个可以用字符串路径执行所有操作的包,所以我最终编写了自己的快速小包,它支持insert()、get()(默认返回)、set()和remove()操作

您可以使用点表示法、括号、数字索引、字符串数字属性以及带有非单词字符的键。简单用法如下:

> var jsocrud = require('jsocrud');

...

// Get (Read) ---
> var obj = {
>     foo: [
>         {
>             'key w/ non-word chars': 'bar'
>         }
>     ]
> };
undefined

> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'


lodash现在使用
\uj.get(obj,property)
支持这一点。请参阅

文档中的示例:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// → 3

_.get(object, ['a', '0', 'b', 'c']);
// → 3

_.get(object, 'a.b.c', 'default');
// → 'default'

这里的解决方案仅用于访问深度嵌套的键。我需要一个用于访问、添加、修改和删除键的解决方案。这就是我想到的:

var deepAccessObject = function(object, path_to_key, type_of_function, value){
    switch(type_of_function){
        //Add key/modify key
        case 0: 
            if(path_to_key.length === 1){
                if(value)
                    object[path_to_key[0]] = value;
                return object[path_to_key[0]];
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    object[path_to_key[0]] = {};
            }
            break;
        //delete key
        case 1:
            if(path_to_key.length === 1){
                delete object[path_to_key[0]];
                return true;
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    return false;
            }
            break;
        default:
            console.log("Wrong type of function");
    }
};
  • path\u to\u key
    :数组中的路径。可以用
    字符串\u path.split(“.”)替换它
  • 类型_
    
    var object = { 'a': [{ 'b': { 'c': 3 } }] };
    
    _.get(object, 'a[0].b.c');
    // → 3
    
    _.get(object, ['a', '0', 'b', 'c']);
    // → 3
    
    _.get(object, 'a.b.c', 'default');
    // → 'default'
    
    /**
     * Access a deep value inside a object 
     * Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
     * @author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
     * Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
     */
    function getDeepVal(obj, path) {
        if (typeof obj === "undefined" || obj === null) return;
        path = path.split(/[\.\[\]\"\']{1,2}/);
        for (var i = 0, l = path.length; i < l; i++) {
            if (path[i] === "") continue;
            obj = obj[path[i]];
            if (typeof obj === "undefined" || obj === null) return;
        }
        return obj;
    }
    
    getDeepVal(obj,'foo.bar')
    getDeepVal(obj,'foo.1.bar')
    getDeepVal(obj,'foo[0].baz')
    getDeepVal(obj,'foo[1][2]')
    getDeepVal(obj,"foo['bar'].baz")
    getDeepVal(obj,"foo['bar']['baz']")
    getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")
    
    var deepAccessObject = function(object, path_to_key, type_of_function, value){
        switch(type_of_function){
            //Add key/modify key
            case 0: 
                if(path_to_key.length === 1){
                    if(value)
                        object[path_to_key[0]] = value;
                    return object[path_to_key[0]];
                }else{
                    if(object[path_to_key[0]])
                        return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                    else
                        object[path_to_key[0]] = {};
                }
                break;
            //delete key
            case 1:
                if(path_to_key.length === 1){
                    delete object[path_to_key[0]];
                    return true;
                }else{
                    if(object[path_to_key[0]])
                        return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                    else
                        return false;
                }
                break;
            default:
                console.log("Wrong type of function");
        }
    };
    
    const deep = { l1: { l2: { l3: "Hello" } } };
    const prop = "l1.l2.l3";
    const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
    // val === "Hello"
    
    const val = _.get(deep, prop);
    
    const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);
    
    function get(obj, path) {
      if(typeof path === 'string') path = path.split('.');
    
      if(path.length === 0) return obj;
      return get(obj[path[0]], path.slice(1));
    }
    
    const obj = {a: {b: {c: 'foo'}}};
    
    console.log(get(obj, 'a.b.c')); //foo
    
    console.log(get(obj, ['a', 'b', 'c'])); //foo
    
    'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)
    
    'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})
    
    'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})
    
    const resolvePath = (object, path, defaultValue) => path
       .split('.')
       .reduce((o, p) => o ? o[p] : defaultValue, object)
    
    resolvePath(window,'document.body') => <body>
    resolvePath(window,'document.body.xyz') => undefined
    resolvePath(window,'document.body.xyz', null) => null
    resolvePath(window,'document.body.xyz', 1) => 1
    
    const setPath = (object, path, value) => path
       .split('.')
       .reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)
    
    let myVar = {}
    setPath(myVar, 'a.b.c', 42) => 42
    console.log(myVar) => {a: {b: {c: 42}}}
    
    const resolvePath = (object, path, defaultValue) => path
       .split(/[\.\[\]\'\"]/)
       .filter(p => p)
       .reduce((o, p) => o ? o[p] : defaultValue, object)
    
    const myVar = {a:{b:[{c:1}]}}
    resolvePath(myVar,'a.b[0].c') => 1
    resolvePath(myVar,'a["b"][\'0\'].c') => 1
    
    if(!Object.prototype.byString){
      //NEW byString which can update values
    Object.prototype.byString = function(s, v, o) {
      var _o = o || this;
          s = s.replace(/\[(\w+)\]/g, '.$1'); // CONVERT INDEXES TO PROPERTIES
          s = s.replace(/^\./, ''); // STRIP A LEADING DOT
          var a = s.split('.'); //ARRAY OF STRINGS SPLIT BY '.'
          for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS
              var k = a[i];
              if (k in _o) {//LOOP THROUGH OBJECT KEYS
                  if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED
                    if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM
                      if(i === a.length -1){//IF IT'S THE LAST IN THE ARRAY
                        _o[k] = v;
                      }
                    }
                    _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE
                  }
              } else {
                  return;
              }
          }
          return _o;
      };
    
    let state = {
            test: "test_value",
            nested: {
                level1: "level1 value"
            },
            arr: [1, 2, 3],
            nested_arr: {
                arr: ["buh", "bah", "foo"]
            }
        }
    
    function handleChange(value, fields) {
        let update_field = state;
        for(var i = 0; i < fields.length - 1; i++){
            update_field = update_field[fields[i]];
        }
        update_field[fields[fields.length-1]] = value;
    }
    
    handleChange("update", ["test"]);
    handleChange("update_nested", ["nested","level1"]);
    handleChange(100, ["arr",0]);
    handleChange('changed_foo', ["nested_arr", "arr", 3]);
    console.log(state);
    
    function valueForKeyPath(obj, path){
            const keys = path.split('.');
            keys.forEach((key)=> obj = obj[key]);
            return obj;
        };
    
    const user = {
        id: 101,
        email: 'jack@dev.com',
        personalInfo: {
            name: 'Jack',
            address: [{
                line1: 'westwish st',
                line2: 'washmasher',
                city: 'wallas',
                state: 'WX'
            }]
        }
    }
    
    const getNestedObject = (nestedObj, pathArr) => {
        return pathArr.reduce((obj, key) =>
            (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
    }
    
    // pass in your object structure as array elements
    const name = getNestedObject(user, ['personalInfo', 'name']);
    
    // to access nested array, just pass in array index as an element the path array.
    const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
    // this will return the city from the first address item.
    
    const city = t(user, 'personalInfo.address[0].city').safeObject;
    
    function Object_Manager(obj, Path, value, Action) 
    {
        try
        {
            if(Array.isArray(Path) == false)
            {
                Path = [Path];
            }
    
            let level = 0;
            var Return_Value;
            Path.reduce((a, b)=>{
                level++;
                if (level === Path.length)
                {
                    if(Action === 'Set')
                    {
                        a[b] = value;
                        return value;
                    }
                    else if(Action === 'Get')
                    {
                        Return_Value = a[b];
                    }
                    else if(Action === 'Unset')
                    {
                        delete a[b];
                    }
                } 
                else 
                {
                    return a[b];
                }
            }, obj);
            return Return_Value;
        }
    
        catch(err)
        {
            console.error(err);
            return obj;
        }
    }
    
     // Set
     Object_Manager(Obj,[Level1,Level2,Level3],New_Value, 'Set');
    
     // Get
     Object_Manager(Obj,[Level1,Level2,Level3],'', 'Get');
    
     // Unset
     Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');
    
    const createPath = (obj, path, value = null) => {
      path = typeof path === 'string' ? path.split('.') : path;
      let current = obj;
      while (path.length > 1) {
        const [head, ...tail] = path;
        path = tail;
        if (current[head] === undefined) {
          current[head] = {};
        }
        current = current[head];
      }
      current[path[0]] = value;
      return obj;
    };
    
    
    var obj = {
      a:{
        b: {
          c:[100,101,{
            d: 1000
          }]
        }
      }
    };
    
    
    var lens = R.lensPath('a.b.c.2.d'.split('.'));
    var result = R.view(lens, obj);