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

675

我有一个类似这样的数据结构:

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的值,即"Part 1"。part2quantity同理,应填写60。

是否有办法使用纯JavaScript或JQuery实现这一点?


不确定你在这里问什么?你想查询part1.name并返回文本"part1.name"吗?还是你想要一种方法来获取存储在part1.name中的值? - BonyT
你尝试过像这样做吗:var part1name = someObject.part1name; - Rafay
1
@BonyT:我想查询someObject.part1.name并返回它的值(“Part 1”)。但是,我希望查询(我称之为“键”)存储在变量'part1name'中。感谢您的回复。@3nigma:我当然可以这样做。但这不是我的意图。感谢回复。 - Komaruloh
1
在重复的答案中,我喜欢fyr的回答。https://dev59.com/Gmoy5IYBdhLWcg3wCpoz - Steve Black
显示剩余2条评论
46个回答

0

使用UnderscorepropertypropertyOf

var test = {
  foo: {
    bar: {
      baz: 'hello'
    }
  }
}
var string = 'foo.bar.baz';


// document.write(_.propertyOf(test)(string.split('.')))

document.write(_.property(string.split('.'))(test));
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

祝你好运...


0

在Alnitak的答案基础上构建:

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

}

这样可以让你设置一个值!

我已经创建了一个npm包GitHub,其中也包括这个功能。


0

React示例 - 使用lodash

从性能角度来看,这可能不是最有效的方法,但如果您的应用程序是一些单体应用程序,那么它肯定会为您节省一些时间。特别是当您将状态数据格式紧密耦合到API后端时。

   import set from "lodash/set";  // More efficient import

    class UserProfile extends Component {

      constructor(props){
        super(props);

        this.state = {
          user: {
            account: {
              id: "",
              email: "",
              first_name: ""
            }
          }
        }
      }

       /**
       * Updates the state based on the form input
       * 
       * @param {FormUpdate} event 
       */
      userAccountFormHook(event) {
        // https://lodash.com/docs#get
        // https://lodash.com/docs#set
        const { name, value } = event.target;
        let current_state = this.state
        set(current_state, name, value)  // Magic happens here
        this.setState(current_state);
      }

    render() {
        return (
          <CustomFormInput
            label: "First Name"
            type: "text"
            placeholder: "First Name"
            name: "user.account.first_name"
            onChange: {this.userAccountFormHook}
            value: {this.state.user.account.first_name}

          />
      )
  }
}

0

从@Alnitak的答案开始,我构建了这个源代码,它下载一个实际的.JSON文件并处理它,在每个步骤中打印解释性字符串,并在传递错误密钥的情况下提供更多详细信息:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  <script>
function retrieveURL(url) {
        var client = new XMLHttpRequest();
        prefix = "https://cors-anywhere.herokuapp.com/"
        client.open('GET', prefix + url);
        client.responseType = 'text';
        client.onload = function() {
            response = client.response; // Load remote response.
            console.log("Response received.");
            parsedJSON  = JSON.parse(response);
            console.log(parsedJSON);
            console.log(JSONitemByPath(parsedJSON,"geometry[6].obs[3].latituade"));
            return response;
        };
        try {
            client.send();
        } catch(e) {
            console.log("NETWORK ERROR!");
            console.log(e);
        }
}



function JSONitemByPath(o, s) {
    structure = "";
    originalString = s;
    console.log("Received string: ", s);
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    console.log("Converted to   : ", s);
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');

    console.log("Single keys to parse: ",a);

    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
            console.log("object." + structure +  a[i], o);
            structure +=  a[i] + ".";
        } else {
            console.log("ERROR: wrong path passed: ", originalString);
            console.log("       Last working level: ", structure.substr(0,structure.length-1));
            console.log("       Contents: ", o);
            console.log("       Available/passed key: ");
            Object.keys(o).forEach((prop)=> console.log("       "+prop +"/" + k));
            return;
        }
    }
    return o;
}


function main() {
    rawJSON = retrieveURL("http://haya2now.jp/data/data.json");
}

</script>
  </head>
  <body onload="main()">
  </body>
</html>

输出示例:

Response received.
json-querier.html:17 {geometry: Array(7), error: Array(0), status: {}}
json-querier.html:34 Received string:  geometry[6].obs[3].latituade
json-querier.html:36 Converted to   :  geometry.6.obs.3.latituade
json-querier.html:40 Single keys to parse:  (5) ["geometry", "6", "obs", "3", "latituade"]
json-querier.html:46 object.geometry (7) [{}, {}, {}, {}, {}, {}, {}]
json-querier.html:46 object.geometry.6 {hayabusa2: {}, earth: {}, obs: Array(6), TT: 2458816.04973593, ryugu: {}, }
json-querier.html:46 object.geometry.6.obs (6) [{}, {}, {}, {}, {}, {}]
json-querier.html:46 object.geometry.6.obs.3 {longitude: 148.98, hayabusa2: {}, sun: {}, name: "DSS-43", latitude: -35.4}
json-querier.html:49 ERROR: wrong path passed:  geometry[6].obs[3].latituade
json-querier.html:50        Last working level:  geometry.6.obs.3
json-querier.html:51        Contents:  {longitude: 148.98, hayabusa2: {}, sun: {}, name: "DSS-43", latitude: -35.4}
json-querier.html:52        Available/passed key: 
json-querier.html:53        longitude/latituade
json-querier.html:53        hayabusa2/latituade
json-querier.html:53        sun/latituade
json-querier.html:53        name/latituade
json-querier.html:53        latitude/latituade
json-querier.html:53        altitude/latituade
json-querier.html:18 undefined

0
阅读了其他答案后,我认为替换_.get()_.set()的最高效且简洁的方法是创建一个具有以下功能的模块。
let rgxBracketToDot;

export function sanitizePath (path) {
    path = path || [];
    return Array.isArray(path) ? path : path.replace(rgxBracketToDot || (rgxBracketToDot = /\[(\w+)\]/g), '.$1').split('.');
}

export function get (obj, path) {
    if (!obj || typeof obj !== 'object') {
        return;
    }
    return sanitizePath(path).reduce((acc, val) => acc && acc[val], obj);
}

export function set (obj, path, value) {
    const [current,...rest] = sanitizePath(path);
    rest.length >= 1 ? set(obj[current] = obj[current] || {}, rest, value) : obj[current]= value;
    return obj;
}

为了与lodash完全兼容,可以在sanitizePath()中包含两个额外的.replace()调用来删除前导和尾随的点(.):
path = path.replace(/^\./, '');
path = path.replace(/\.$/, '');

这应该以类似于 rgxBracketToDot 的方式完成,以便正则表达式只设置一次。
如果您对路径参数有完全控制,还可以通过仅使用数组路径并完全删除 sanitizeString() 来提高代码的性能。

0
另一个解决方案:
export function getNestedFieldByStringKey(obj, path) {
  const squareBracketsRgx = /\[(\w|'|")*\]/g
  const squareBracketsToDot = (sb: string) => `.${sb.replace(/\[|\]|'|"/g, '')}`
  const parts = path
    .replace(squareBracketsRgx, squareBracketsToDot)
    .split('.')

  return parts.reduce((o, part) => o?.[part], obj)
}

这是一些测试:
describe('getNestedFieldByStringKey', () => {
    it('should return the nested field using "." as separator', () => {
      const input = {
        some: {
          example: {
            nested: true
          }
        }
      }

      expect(getNestedFieldByStringKey(input, 'some.example.nested')).toBe(true)
    })

    it('should return the nested field using "." and "[]" as separator', () => {
      const input = {
        some: {
          example: {
            nested: [{ test: true }]
          }
        }
      }

      expect(getNestedFieldByStringKey(input, 'some["example"].nested[0].test')).toBe(true)
    })

    it('should return undefined if does not exist', () => {
      const input = {}

      expect(getNestedFieldByStringKey(input, 'some["example"].nested[0].test')).toBe(undefined)
    })
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接