【问题标题】:Access Javascript nested objects safely安全访问 Javascript 嵌套对象
【发布时间】:2013-08-13 06:11:18
【问题描述】:

我有基于 json 的数据结构,其中的对象包含嵌套对象。为了访问特定的数据元素,我将对象属性的引用链接在一起。例如:

var a = b.c.d;

如果 b 或 b.c 未定义,这将失败并出现错误。但是,如果它存在,我想获得一个值,否则只是未定义。无需检查链中的每个值是否都存在的最佳方法是什么?

我希望尽可能保持这种方法的通用性,这样我就不必添加大量的辅助方法,例如:

var a = b.getD();

var a = helpers.getDFromB(b);

我还想尽量避免使用 try/catch 构造,因为这不是错误,因此使用 try/catch 似乎是错误的。这合理吗?

有什么想法吗?

【问题讨论】:

标签: javascript node.js


【解决方案1】:

ES6 有optional chaining,可以这样使用:

const object = { foo: {bar: 'baz'} };

// not found, undefined
console.log(object?.foo?.['nested']?.missing?.prop)

// not found, object as default value
console.log(object?.foo?.['nested']?.missing?.prop || {})

// found, "baz"
console.log(object?.foo?.bar)

这种方法需要定义变量“object”并成为一个对象。

或者,您可以定义自己的实用程序,这是一个实现递归的示例:

const traverseObject = (object, propertyName, defaultValue) => {
  if (Array.isArray(propertyName)) {
    return propertyName.reduce((o, p) => traverseObject(o, p, defaultValue), object);
  }

  const objectSafe = object || {};

  return objectSafe[propertyName] || defaultValue;
};

// not found, undefined
console.log(traverseObject({}, 'foo'));

// not found, object as default value
console.log(traverseObject(null, ['foo', 'bar'], {}));

// found "baz"
console.log(traverseObject({foo: {bar:'baz'}}, ['foo','bar']));

【讨论】:

    【解决方案2】:

    ECMAScript2020,在 Node v14 中,有 optional chaining 运算符(我见过它也称为安全导航运算符),这将使您的示例可以写成:

    var a = b?.c?.d;

    来自MDN docs

    可选的链接运算符 (?.) 允许读取位于连接对象链深处的属性值,而无需明确验证链中的每个引用是否有效。这 ?。运算符的功能类似于 .链接运算符,除了如果引用为空(null 或未定义)而不是导致错误,表达式短路并返回未定义的值。与函数调用一起使用时,如果给定函数不存在,则返回 undefined。

    【讨论】:

    • 这是最相关和可读性最高的解决方案
    【解决方案3】:

    我只是将我在几乎所有项目中使用的函数粘贴为这种情况的实用程序。

    public static is(fn: Function, dv: any) {
        try {
            if (fn()) {
                    return fn()
                } else {
                    return dv
                }
            } catch (e) {
                return dv
            }
        }
    

    所以第一个参数是回调,第二个是默认值,如果由于某些错误而无法提取数据。

    我在所有地方都这样称呼它:

    var a = is(()=> a.b.c, null);
    

    【讨论】:

      【解决方案4】:
      const getValue = (obj, property, defaultValue) => (
        property.split('.').reduce((item, key) => {
          if (item && typeof item === 'object' && key in item) {
            return item[key];
          }
          return defaultValue;
        }, obj)
      )
      

      const object = { 'a': { 'b': { 'c': 3 } } };

      getValue(object, 'a.b.c'); // 3
      getValue(object, 'a.b.x'); // undefined
      getValue(object, 'a.b.x', 'default'); // 'default'
      getValue(object, 'a.x.c'); // undefined
      

      【讨论】:

        【解决方案5】:

        这里的答案是很好的裸机解决方案。但是,如果您只想使用经过验证的包,我建议使用 lodash。

        使用 ES6,您可以运行以下命令

        import _ from 'lodash'
        
        var myDeepObject = {...}
        
        value = _.get(myDeepObject, 'maybe.these.path.exist', 'Default value if not exists')
        
        

        【讨论】:

          【解决方案6】:

          如果您关心语法,这里是 Hosar 答案的更简洁版本:

          function safeAccess(path, object) {
            if (object) {
              return path.reduce(
                (accumulator, currentValue) => (accumulator && accumulator[currentValue] ? accumulator[currentValue] : null),
                object,
              );
            } else {
              return null;
            }
          }
          

          【讨论】:

            【解决方案7】:

            一个老问题,现在我们经常有 Typescript 项目,以至于这个问题似乎无关紧要,但我来到这里是为了寻找同样的东西,所以我做了一个简单的函数来做。你对不使用 try/catch 的想法对我来说太严格了,毕竟寻找undefined.x 无论如何都会导致错误。 综上所述,这就是我的方法。

            function getSafe (obj, valuePath) {
                try { return eval("obj." + valuePath); } 
                catch (err) { return null; }
            }
            

            要使用它,我们必须传递对象。我试图避免这种情况,但是没有其他方法可以从另一个函数中获取范围(这里有很多关于这个的问题)。 还有一个小测试集来看看我们得到了什么:

            let outsideObject = {
                html: {
                    pageOne: {
                        pageTitle: 'Lorem Ipsum!'
                    }
                }
            };
            function testme() {  
                let insideObject = { a: { b: 22 } };
                return {
                    b: getSafe(insideObject, "a.b"),       // gives: 22
                    e: getSafe(insideObject, "a.b.c.d.e"), // gives: null
                    pageTitle: getSafe(outsideObject, "html.pageOne.pageTitle"),     // gives: Lorem Ipsum!
                    notThere: getSafe(outsideObject, "html.pageOne.pageTitle.style") // gives: undefined
                }
            }
            testme(); 
            

            更新: 关于eval的使用,我认为eval是一个谨慎使用的工具,而不是魔鬼本身。在这种方法中,用户不会干扰 eval,因为开发人员正在通过名称查找属性。

            【讨论】:

              【解决方案8】:

              获取pathobject 的值。如果解析的值为undefined,则返回defaultValue

              在 ES6 中,我们可以从 Object 获取嵌套属性,如下所示code sn-p

              const myObject = {
                  a: {
                    b: {
                      c: {
                        d: 'test'
                      }
                    }
                  },
                  c: {
                    d: 'Test 2'
                  }
                },
              
                isObject = obj => obj && typeof obj === 'object',
              
                hasKey = (obj, key) => key in obj;
              
              
              
              function nestedObj(obj, property, callback) {
                return property.split('.').reduce((item, key) => {
                  if (isObject(item) && hasKey(item, key)) {
                    return item[key];
                  }
                  return typeof callback != undefined ? callback : undefined;
                }, obj);
              }
              
              console.log(nestedObj(myObject, 'a.b.c.d')); //return test
              
              
              console.log(nestedObj(myObject, 'a.b.c.d.e')); //return undefined
              
              
              console.log(nestedObj(myObject, 'c.d')); //return Test 2
              
              
              console.log(nestedObj(myObject, 'd.d', false)); //return false
              
              
              console.log(nestedObj(myObject, 'a.b')); //return {"c": {"d": "test"}}

              【讨论】:

                【解决方案9】:

                可能很简单:

                let a = { a1: 11, b1: 12, c1: { d1: 13, e1: { g1: 14 }}}
                console.log((a || {}).a2); => undefined
                console.log(((a || {}).c1 || {}).d1) => 13
                

                等等。

                【讨论】:

                  【解决方案10】:

                  这是一个老问题,现在有了 es6 的特性,这个问题可以更容易地解决。

                  const idx = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
                  

                  感谢@sharifsbeat 提供此solution

                  【讨论】:

                    【解决方案11】:

                    如果你想在手头有不规则数量的属性的动态访问,在 ES6 中你可以轻松地执行以下操作;

                    function getNestedValue(o,...a){
                      var val = o;
                      for (var prop of a) val = typeof val === "object" &&
                                                       val !== null     &&
                                                       val[prop] !== void 0 ? val[prop]
                                                                            : undefined;
                      return val;
                    }
                    var obj = {a:{foo:{bar:null}}};
                    console.log(getNestedValue(obj,"a","foo","bar"));
                    console.log(getNestedValue(obj,"a","hop","baz"));

                    【讨论】:

                      【解决方案12】:

                      标准方法:

                      var a = b && b.c && b.c.d && b.c.d.e;
                      

                      相当快但不太优雅(特别是对于较长的属性名称)。

                      使用函数遍历 JavaScipt 对象属性既不高效也不优雅。

                      试试这个:

                      try { var a = b.c.d.e; } catch(e){}
                      

                      如果您确定 a 以前没有使用过或

                      try { var a = b.c.d.e; } catch(e){ a = undefined; }
                      

                      如果你以前分配过它。

                      这可能比第一个选项更快。

                      【讨论】:

                        【解决方案13】:

                        您可以创建一个通用方法,该方法基于属性名称数组访问元素,该数组被解释为通过属性的路径:

                        function getValue(data, path) {
                            var i, len = path.length;
                            for (i = 0; typeof data === 'object' && i < len; ++i) {
                                data = data[path[i]];
                            }
                            return data;
                        }
                        

                        那么你可以这样称呼它:

                        var a = getValue(b, ["c", "d"]);
                        

                        【讨论】:

                        • 鉴于 a.b === a['b'] 我认为使用字符串引用对象被认为是 JS 中的最佳实践?
                        • @Hubris - 属性名称始终是字符串。点符号被认为是“语法糖”(如果属性名称是保留字或不是有效的 JS 标识符,它甚至不起作用)。
                        • 好吧,我就是这么想的。惊人的。谢谢你:)
                        • @Hubris - 注意 Chris 的评论;它链接到一个使用相同想法但更漂亮的答案。它允许您传递一个像“c.d”这样的字符串,然后按照指示的路径为您解析它。
                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2012-11-25
                        • 1970-01-01
                        • 1970-01-01
                        • 2022-08-18
                        • 2022-01-08
                        • 1970-01-01
                        相关资源
                        最近更新 更多