[英]Access Javascript nested objects safely
I have json based data structure with objects containing nested objects.我有基于 json 的数据结构,其中包含嵌套对象的对象。 In order to access a particular data element I have been chaining references to object properties together.为了访问特定的数据元素,我将对象属性的引用链接在一起。 For example:例如:
var a = b.c.d;
If b or bc is undefined, this will fail with an error.如果 b 或 bc 未定义,这将失败并出现错误。 However, I want to get a value if it exists otherwise just undefined.但是,如果它存在,我想获得一个值,否则只是未定义。 What is the best way to do this without having to check that every value in the chain exists?无需检查链中的每个值是否都存在的最佳方法是什么?
I would like to keep this method as general as possible so I don't have to add huge numbers of helper methods like:我想尽可能地保持这种方法的通用性,这样我就不必添加大量的辅助方法,例如:
var a = b.getD();
or或者
var a = helpers.getDFromB(b);
I also want to try to avoid try/catch constructs as this isn't an error so using try/catch seems misplaced.我还想尽量避免使用 try/catch 构造,因为这不是错误,所以使用 try/catch 似乎是错误的。 Is that reasonable?这合理吗?
Any ideas?有任何想法吗?
Standard approach:标准方法:
var a = b && b.c && b.c.d && b.c.d.e;
is quite fast but not too elegant (especially with longer property names).非常快但不太优雅(特别是对于较长的属性名称)。
Using functions to traverse JavaScipt object properties is neither efficient nor elegant.使用函数遍历 JavaScipt 对象属性既不高效也不优雅。
Try this instead:试试这个:
try { var a = b.c.d.e; } catch(e){}
in case you are certain that a
was not previously used or如果您确定a
以前没有使用过或
try { var a = b.c.d.e; } catch(e){ a = undefined; }
in case you may have assigned it before.以防您以前分配过它。
This is probably even faster that the first option.这可能比第一个选项更快。
ECMAScript2020 , and in Node v14, has the optional chaining operator (I've seen it also called safe navigation operator), which would allow your example to be written as: ECMAScript2020在 Node v14 中具有可选的链接运算符(我已经看到它也称为安全导航运算符),这将允许您的示例编写为:
var a = b?.c?.d;
The optional chaining operator (?.) permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid.可选的链接运算符 (?.) 允许读取位于连接对象链深处的属性值,而无需明确验证链中的每个引用是否有效。 The ?.这 ?。 operator functions similarly to the .运算符的功能与 . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined.链接运算符,除了如果引用为空(null 或未定义)时不会导致错误,表达式会短路并返回未定义的值。 When used with function calls, it returns undefined if the given function does not exist.与函数调用一起使用时,如果给定函数不存在,则返回 undefined。
You can create a general method that access an element based on an array of property names that is interpreted as a path through the properties:您可以创建一个通用方法,该方法基于属性名称数组访问元素,该数组被解释为通过属性的路径:
function getValue(data, path) {
var i, len = path.length;
for (i = 0; typeof data === 'object' && i < len; ++i) {
data = data[path[i]];
}
return data;
}
Then you could call it with:然后你可以调用它:
var a = getValue(b, ["c", "d"]);
ES6 has optional chaining which can be used as follows: 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)
This approach requires the variable "object" to be defined and to be an object.这种方法需要定义变量“object”并使其成为一个对象。
Alternatively, you could define your own utility, here's an example which implements recursion:或者,您可以定义自己的实用程序,这是一个实现递归的示例:
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']));
probably it's may be simple:可能很简单:
let a = { a1: 11, b1: 12, c1: { d1: 13, e1: { g1: 14 }}}
console.log((a || {}).a2); => undefined
console.log(((a || {}).c1 || {}).d1) => 13
and so on.等等。
The answers here are good bare-metal solutions.这里的答案是很好的裸机解决方案。 However, if you just want to use a package that is tried and true, I recommend using lodash.但是,如果您只想使用经过验证的包,我建议使用 lodash。
With ES6 you can run the following使用 ES6,您可以运行以下命令
import _ from 'lodash'
var myDeepObject = {...}
value = _.get(myDeepObject, 'maybe.these.path.exist', 'Default value if not exists')
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
I will just paste the function that I use in almost all project as utility for this type of situation.我将粘贴我在几乎所有项目中使用的功能作为此类情况的实用程序。
public static is(fn: Function, dv: any) {
try {
if (fn()) {
return fn()
} else {
return dv
}
} catch (e) {
return dv
}
}
So first argument is callback and second is the default value if it fails to extract the data due to some error.所以第一个参数是回调,第二个是默认值,如果由于某些错误而无法提取数据。
I call it at all places as follows:我在所有地方都这样称呼它:
var a = is(()=> a.b.c, null);
// 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);
}
If you would like to have a dynamic access with irregular number of properties at hand, in ES6 you might easily do as follows;如果你想要动态访问手头上不规则数量的属性,在 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"));
Gets the value at path
of object
.获取object
path
的值。 If the resolved value is undefined
, the defaultValue
is returned in its place.如果解析的值是undefined
,则在其位置返回defaultValue
。
In ES6 we can get nested property from an
Object
like below code snippet .在 ES6 中,我们可以从Object
中获取嵌套属性,如下面的代码片段。
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, 'abcd')); //return test console.log(nestedObj(myObject, 'abcde')); //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"}}
An old question, and now days we have Typescript projects so often that this question seems irrelevant, but I got here searching for the same thing, so I made a simple function to do it.一个老问题,现在我们经常有 Typescript 项目,以至于这个问题似乎无关紧要,但我来这里是为了寻找同样的东西,所以我做了一个简单的函数来做。 Your thoughts about not using try/catch is too strict for my taste, after all the seek for undefined.x
will cause an error anyway.你对不使用 try/catch 的想法对我来说太严格了,毕竟寻找undefined.x
无论如何都会导致错误。 So with all that, this is my method.因此,这就是我的方法。
function getSafe (obj, valuePath) {
try { return eval("obj." + valuePath); }
catch (err) { return null; }
}
To use this we have to pass the object.要使用它,我们必须传递对象。 I tried to avoid that, but there was not other way to get scope into it from another function (there is a whole bunch of questions about this in here).我试图避免这种情况,但没有其他方法可以从另一个函数中获取范围(这里有很多关于这个的问题)。 And a small test set to see what we get:还有一个小测试集来看看我们得到了什么:
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();
UPDATE: Regarding the use of eval
I think that eval is a tool to use carefully and not the devil itself.更新:关于eval
的使用,我认为 eval 是一个谨慎使用的工具,而不是魔鬼本身。 In this method, the user does not interfere with eval since it is the developer that is looking for a property by its name.在这种方法中,用户不会干扰 eval,因为开发人员正在通过名称查找属性。
If you care about syntax, here's a cleaner version of Hosar's answer:如果您关心语法,这里是 Hosar 答案的更简洁版本:
function safeAccess(path, object) {
if (object) {
return path.reduce(
(accumulator, currentValue) => (accumulator && accumulator[currentValue] ? accumulator[currentValue] : null),
object,
);
} else {
return null;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.