安全地访问JavaScript嵌套对象

26

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

var a = b.c.d;
如果b或b.c未定义,这将失败并出现错误。 但是,我希望在存在值时获取该值,否则只返回undefined。在不必检查链中每个值是否存在的情况下,最好的方法是什么?
我希望尽可能保持此方法的通用性,以便我不必添加大量的辅助方法,例如:
var a = b.getD();
或者
var a = helpers.getDFromB(b);

我也希望尝试避免使用try/catch结构,因为这不是一个错误,所以使用try/catch似乎不合适。这样做合理吗?

有什么想法吗?


任何想法?

2
https://dev59.com/EGw15IYBdhLWcg3wntOh?rq=1 - goat
16个回答

28

ECMAScript2020和Node v14中,有一个可选链操作符(我也看到它被称为安全导航操作符),可以使你的示例写成:

var a = b?.c?.d;

来自MDN文档:

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


这是最相关和可读的解决方案。 - TechWisdom

19

标准方法:

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

速度相当快,但不太优雅(特别是在使用较长的属性名称时)。

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

尝试使用以下方法:

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

如果您确定a以前未被使用,或者

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

如果之前已经分配过,那就无需再次分配。

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


9

ES6具有可选链,可按以下方式使用:

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


5
您可以创建一个通用方法,根据属性名称的数组访问元素,这些属性名称被解释为通过属性的路径:
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
@Hubris - 属性名始终是字符串。点符号表示法被认为是“语法糖”(如果属性名是保留字或不是有效的JS标识符,则甚至无法工作)。 - Ted Hopp
好的,这正是我想的。太棒了。谢谢 :) - Hubris
1
@Hubris - 注意Chris的评论,它链接到一个使用相同思路但更加复杂的答案。它允许您传递一个单独的字符串,比如"c.d",然后为您解析它,按照指定的路径前进。 - Ted Hopp

3

这是一个老问题,现在有了es6功能,这个问题可以更轻松地解决。

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

Thanks to @sharifsbeat for this solution.


3
这里提供的答案都是很好的裸机解决方案。但是,如果你只想使用一种经过验证的包,我建议使用lodash。
在ES6中,您可以运行以下内容。
import _ from 'lodash'

var myDeepObject = {...}

value = _.get(myDeepObject, 'maybe.these.path.exist', 'Default value if not exists')


2
也许这很简单:
let a = { a1: 11, b1: 12, c1: { d1: 13, e1: { g1: 14 }}}
console.log((a || {}).a2); => undefined
console.log(((a || {}).c1 || {}).d1) => 13

等等。


0

// The code for the regex isn't great, 
// but it suffices for most use cases.

/**
 * Gets the value at `path` of `object`.
 * If the resolved value is `undefined`,
 * or the property does not exist (set param has: true),
 * the `defaultValue` is returned in its place.
 *
 * @param {Object} object The object to query.
 * @param {Array|string} path The path of the property to get.
 * @param {*} [def] The value returned for `undefined` resolved values.
 * @param {boolean} [has] Return property instead of default value if key exists.
 * @returns {*} Returns the resolved value.
 * @example
 *
 * var object = { 'a': [{ 'b': { 'c': 3 } }], b: {'c-[d.e]': 1}, c: { d: undefined, e: 0 } };
 *
 * dotGet(object, 'a[0].b.c');
 * // => 3
 * 
 * dotGet(object, ['a', '0', 'b', 'c']);
 * // => 3
 *
 * dotGet(object, ['b', 'c-[d.e]']);
 * // => 1
 *
 * dotGet(object, 'c.d', 'default value');
 * // => 'default value'
 *
 * dotGet(object, 'c.d', 'default value', true);
 * // => undefined
 *
 * dotGet(object, 'c.d.e', 'default value');
 * // => 'default value'
 *
 * dotGet(object, 'c.d.e', 'default value', true);
 * // => 'default value'
 *
 * dotGet(object, 'c.e') || 5; // non-true default value
 * // => 5 
 * 
 */
var dotGet = function (obj, path, def, has) {
    return (typeof path === 'string' ? path.split(/[\.\[\]\'\"]/) : path)
    .filter(function (p) { return 0 === p ? true : p; })
    .reduce(function (o, p) {
        return typeof o === 'object' ? ((
            has ? o.hasOwnProperty(p) : o[p] !== undefined
        ) ? o[p] : def) : def;
    }, obj);
}


1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

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

常量对象 = {'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

0
在我的情况下,结构赋值、数组和对象的扩展语法的组合效果很好。

let json = {a: 1, b: {c: 2, d: 3, e: 4}};

let traverse = ([prop, ...props], {[prop]: obj} = {}) => 
  props.length ? traverse(props, obj) : obj;

let w = traverse(['b', 'd'], json);       // 3
let x = traverse(['x', 'd'], json);       // undefined (parent prop x missing)
let y = traverse(['b', 'x'], json);       // undefined (child prop x missing)
let z = traverse(['b', 'd', 'x'], json);  // undefined (child prop x missing)
console.log(w);
console.log(x);
console.log(y);
console.log(z)


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