[英]Access Javascript nested objects safely
我有基於 json 的數據結構,其中包含嵌套對象的對象。 為了訪問特定的數據元素,我將對象屬性的引用鏈接在一起。 例如:
var a = b.c.d;
如果 b 或 bc 未定義,這將失敗並出現錯誤。 但是,如果它存在,我想獲得一個值,否則只是未定義。 無需檢查鏈中的每個值是否都存在的最佳方法是什么?
我想盡可能地保持這種方法的通用性,這樣我就不必添加大量的輔助方法,例如:
var a = b.getD();
或者
var a = helpers.getDFromB(b);
我還想盡量避免使用 try/catch 構造,因為這不是錯誤,所以使用 try/catch 似乎是錯誤的。 這合理嗎?
有任何想法嗎?
標准方法:
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; }
以防您以前分配過它。
這可能比第一個選項更快。
ECMAScript2020在 Node v14 中具有可選的鏈接運算符(我已經看到它也稱為安全導航運算符),這將允許您的示例編寫為:
var a = b?.c?.d;
來自MDN 文檔:
可選的鏈接運算符 (?.) 允許讀取位於連接對象鏈深處的屬性值,而無需明確驗證鏈中的每個引用是否有效。 這 ?。 運算符的功能與 . 鏈接運算符,除了如果引用為空(null 或未定義)時不會導致錯誤,表達式會短路並返回未定義的值。 與函數調用一起使用時,如果給定函數不存在,則返回 undefined。
您可以創建一個通用方法,該方法基於屬性名稱數組訪問元素,該數組被解釋為通過屬性的路徑:
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"]);
這是一個老問題,現在有了 es6 的特性,這個問題可以更容易地解決。
const idx = (p, o) => p.reduce((xs, x) => (xs && xs[x]) ? xs[x] : null, o);
感謝@sharifsbeat 提供此解決方案。
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']));
可能很簡單:
let a = { a1: 11, b1: 12, c1: { d1: 13, e1: { g1: 14 }}}
console.log((a || {}).a2); => undefined
console.log(((a || {}).c1 || {}).d1) => 13
等等。
這里的答案是很好的裸機解決方案。 但是,如果您只想使用經過驗證的包,我建議使用 lodash。
使用 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)
)
常量對象 = { '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
我將粘貼我在幾乎所有項目中使用的功能作為此類情況的實用程序。
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);
// 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);
}
如果你想要動態訪問手頭上不規則數量的屬性,在 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"));
獲取object
path
的值。 如果解析的值是undefined
,則在其位置返回defaultValue
。
在 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"}}
一個老問題,現在我們經常有 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,因為開發人員正在通過名稱查找屬性。
如果您關心語法,這里是 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.