【问题标题】:Accessing nested JavaScript objects and arrays by string path通过字符串路径访问嵌套的 JavaScript 对象和数组
【发布时间】:2011-09-23 09:41:09
【问题描述】:

我有这样的数据结构:

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”。与填充 60 的 part2quantity 相同。

有没有办法用纯 javascript 或 JQuery 实现这一点?

【问题讨论】:

  • 不确定你在这里问什么?您希望能够查询 part1.name 并返回文本“part1.name”吗?或者您想要一种方法来获取存储在 part1.name 中的值?
  • 你试过像var part1name = someObject.part1name;`
  • @BonyT :我想查询 someObject.part1.name 并返回它的值(“Part 1”)。但是,我希望将查询(我称之为“密钥”)存储在变量“part1name”中。感谢您的回复。 @3nigma:我当然有。但这不是我的意图。感谢您的回复。
  • 在重复的答案中,我喜欢 fyr 的答案stackoverflow.com/questions/8817394/…

标签: javascript path nested


【解决方案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];

等价于

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

var part1name = someObject.part1.name;

我希望这能解决您的问题...

编辑

我不会使用字符串来维护某种 xpath 查询 来访问对象值。 由于您必须调用一个函数来解析查询并检索我将遵循另一条路径的值(不是:

var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 =  function() { return this.part3[0]['name'];}

// usage: part1name.apply(someObject);

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

var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 =  function(obj) { return obj.part3[0]['name'];}

// usage: part1name(someObject);

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

顺便说一句,我觉得在正确的时间做一个简单的任务就足够了......

【讨论】:

  • 有趣。但在我的情况下,当我为 part1name 赋值时,someObject 尚未初始化。我只知道结构。这就是我使用字符串来描述结构的原因。并希望能够使用它从 someObject 中查询我的数据。感谢您分享您的想法。 :)
  • @Komaruloh :我认为您会在创建变量时写下该对象尚未初始化。顺便说一句,我不明白,你为什么不能在适当的时候做作业?
  • 很抱歉没有提到 someObject 还没有初始化。至于原因, someObject 是通过 Web 服务获取的。我想要一个包含 part1name、part2qty 等的 header 数组。这样我就可以遍历 header 数组并根据 part1name 值作为 someObject 的“键”/路径获取我想要的值。跨度>
【解决方案2】:

这个解决方案怎么样:

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

可能有些人会认为它们不安全,但它们解析字符串的速度肯定要快得多。

【讨论】:

  • +1,当性能很重要时,这是一个完全有效的答案,您可以提前创建 getter 函数并保证字符串是安全的。虽然我会使用new Function,但它更安全一些,因为它无法访问其范围之外的变量。我们每次查找的速度提高了 10 倍。
【解决方案3】:

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

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_to_key:数组中的路径。您可以将其替换为您的 string_path.split(".")
  • type_of_function:0 用于访问(不要将任何值传递给value),0 用于添加和修改。 1 表示删除。

【讨论】:

    【解决方案4】:

    基于 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 packagegithub

    【讨论】:

      【解决方案5】:

      可以使用数组代替字符串来处理嵌套对象和数组,例如:["my_field", "another_field", 0, "last_field", 10]

      这是一个基于此数组表示更改字段的示例。我在 react.js 中使用类似的东西来改变嵌套结构的状态的受控输入字段。

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

      【讨论】:

        【解决方案6】:

        使用UnderscorepropertypropertyOf

        var test = {
          foo: {
            bar: {
              baz: 'hello'
            }
          }
        }
        var string = 'foo.bar.baz';
        
        
        // document.write(_.propertyOf(test)(string.split('.')))
        
        document.write(_.property(string.split('.'))(test));
        &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"&gt;&lt;/script&gt;

        祝你好运……

        【讨论】:

          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            AngularJS 有$scope.$eval

            使用 AngularJS,可以使用$scope.$eval 方法访问嵌套对象:

            $scope.someObject = someObject;
            console.log( $scope.$eval("someObject.part3[0].name") ); //Part 3A
            

            有关详细信息,请参阅

            演示

            angular.module("app",[])
            .run(function($rootScope) {
                 $rootScope.someObject = {
                     'part2' : {
                          'name': 'Part 2',
                          'size': '15',
                          'qty' : '60'
                     },
                     'part3' : [{
                          'name': 'Part 3A',
                          'size': '10',
                          'qty' : '20'
                     },{
                          name: 'Part 3B'           
                     }]
                 };
                 console.log(
                     "part3[0].name =",
                     $rootScope.$eval("someObject.part3[0].name")
                );
            })
            <script src="//unpkg.com/angular/angular.js"></script>
            <body ng-app="app"
            </body>

            【讨论】:

              【解决方案9】:

              请注意,以下内容不适用于所有有效的 unicode 属性名称(但据我所知,其他任何答案都不适用)。

              const PATTERN = /[\^|\[|\.]([$|\w]+)/gu
              
              function propValue(o, s) {
                  const names = []
                  for(let [, name] of [...s.matchAll(PATTERN)]) 
                      names.push(name)
                  return names.reduce((p, propName) => {
                      if(!p.hasOwnProperty(propName)) 
                          throw 'invalid property name'
                      return p[propName]
                  }, o)
              }
              
              let path = 'myObject.1._property2[0][0].$property3'
              let o = {
                  1: {
                      _property2: [
                          [{
                              $property3: 'Hello World'
                          }]
                      ]
                  }
              }
              console.log(propValue(o, path)) // 'Hello World'

              【讨论】:

                【解决方案10】:

                使用object-scan 这将成为一个单行。但更重要的是,此解决方案考虑了性能:

                • 在搜索过程中遍历了一次输入(即使查询了多个键)
                • 解析只在初始化时发生一次(以防查询多个对象)
                • 允许使用* 扩展语法

                // const objectScan = require('object-scan');
                
                const 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' }] };
                
                const get = (haystack, needle) => objectScan([needle], { rtn: 'value', abort: true })(haystack);
                
                console.log(get(someObject, 'part1.name'));
                // => Part 1
                console.log(get(someObject, 'part2.qty'));
                // => 60
                console.log(get(someObject, 'part3[0].name'));
                // => Part 3A
                
                const getAll = (haystack, ...needles) => objectScan(needles, { reverse: false, rtn: 'entry', joined: true })(haystack);
                
                console.log(getAll(someObject, 'part1.name', 'part2.qty', 'part3[0].name'));
                /* =>
                [ [ 'part1.name', 'Part 1' ],
                  [ 'part2.qty', '60' ],
                  [ 'part3[0].name', 'Part 3A' ] ]
                 */
                
                console.log(getAll(someObject, 'part1.*'));
                /* =>
                [ [ 'part1.name', 'Part 1' ],
                  [ 'part1.size', '20' ],
                  [ 'part1.qty', '50' ] ]
                 */
                .as-console-wrapper {max-height: 100% !important; top: 0}
                &lt;script src="https://bundle.run/object-scan@13.8.0"&gt;&lt;/script&gt;

                免责声明:我是object-scan的作者

                【讨论】:

                  【解决方案11】:

                  我的解决方案基于@AdrianoSpadoni 给出的解决方案,并解决了克隆对象的需求

                  function generateData(object: any, path: string, value: any): object {
                    const clone = JSON.parse(JSON.stringify(object));
                    path
                      .split(".")
                      .reduce(
                      (o, p, i) => (o[p] = path.split(".").length === ++i ? value : o[p] || {}),
                    clone
                  );
                    return clone;
                  }
                  

                  【讨论】:

                  • 谢谢@AdrianoSpadoni :+1
                  【解决方案12】:

                  从@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
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2023-03-14
                    • 1970-01-01
                    相关资源
                    最近更新 更多