دسترسی به اشیاء و آرایههای تو در توی جاوااسکریپت از طریق مسیر رشتهای


من یک ساختار دادهای مثل این دارم:

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" است. همان کار با part2quantity که با 60 پر شده است، انجام میشود.

آیا راهی وجود دارد تا این کار را با جاوااسکریپت خالص یا جیکوئری انجام داد؟

پاسخ شماره ۱

من این را فقط بر اساس برخی کدهای مشابهی که قبلاً داشتم ساختم، به نظر میرسد کار میکند:

Object.byString = function(o, s) {
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');
    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
        } else {
            return;
        }
    }
    return o;
}

استفاده::

Object.byString(someObj, 'part3[0].name');

یک نسخه نمایشی کارکردی را در http://jsfiddle.net/alnitak/hEsys/ ببینید

ویرایش برخی متوجه شدهاند که این کد در صورتی که یک رشته ارسال شود و اندیسهای سمت چپ با یک ورودی درست تو در تو درون شیء مطابقت نداشته باشند، خطا خواهد داد. این یک نگرانی معتبر است، اما به نظر من بهترین راه حل استفاده از یک بلوک try/catch هنگام فراخوانی است، نه اینکه این تابع بهصورت بیصدا undefined را برای یک اندیس نامعتبر برگرداند.

پاسخ شماره ۲

این اکنون توسط lodash با استفاده از _.get(obj, property) پشتیبانی میشود. ببینید https://lodash.com/docs#get

نمونهای از مستندات:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');
// → 3

_.get(object, ['a', '0', 'b', 'c']);
// → 3

_.get(object, 'a.b.c', 'default');
// → 'default'

پاسخ شماره ۳

این راهحل است که من استفاده میکنم:

function resolve(path, obj=self, separator='.') {
    var properties = Array.isArray(path) ? path : path.split(separator)
    return properties.reduce((prev, curr) => prev?.[curr], obj)
}

نمونه استفاده:

// accessing property path on global scope
resolve("document.body.style.width")
// or
resolve("style.width", document.body)

// accessing array indexes
// (someObject has been defined in the question)
resolve("part3.0.size", someObject) // returns '10'

// accessing non-existent properties
// returns undefined when intermediate properties are not defined:
resolve('properties.that.do.not.exist', {hello:'world'})

// accessing properties with unusual keys by changing the separator
var obj = { object: { 'a.property.name.with.periods': 42 } }
resolve('object->a.property.name.with.periods', obj, '->') // returns 42

// accessing properties with unusual keys by passing a property name array
resolve(['object', 'a.property.name.with.periods'], obj) // returns 42

محدودیتها:

  • نمیتوان از کروشهها ([]) برای شاخصهای آرایه استفاده کرد—اگرچه مشخص کردن شاخصهای آرایه بین نشانه جداکننده (مثلاً .) همانطور که در بالا نشان داده شد، به خوبی کار میکند.

پاسخ شماره ۴

ES6: تنها یک خط در ونیلا JS (اگر پیدا نکند null برمیگرداند به جای اینکه خطا بدهد):

'path.string'.split('.').reduce((p,c)=>p&&p[c]||null, MyOBJ)

به عنوان مثال:

'a.b.c'.split('.').reduce((p,c)=>p&&p[c]||null, {a:{b:{c:1}}})

با عملگر زنجیرهای اختیاری:

'a.b.c'.split('.').reduce((p,c)=>p?.[c], {a:{b:{c:1}}})

برای یک تابع آماده که همچنین مقادیر نادرست را شناسایی کند و مقادیر پیشفرض را به عنوان پارامتر بپذیرد:

const resolvePath = (object, path, defaultValue) => path
   .split('.')
   .reduce((o, p) => o ? o[p] : defaultValue, object)

نمونهای برای استفاده:

resolvePath(window,'document.body') => <body>
resolvePath(window,'document.body.xyz') => undefined
resolvePath(window,'document.body.xyz', null) => null
resolvePath(window,'document.body.xyz', 1) => 1

پاداش:

برای تنظیم یک مسیر (درخواست شده توسط @rob-gordon) میتوانید از این استفاده کنید:

const setPath = (object, path, value) => path
   .split('.')
   .reduce((o,p,i) => o[p] = path.split('.').length === ++i ? value : o[p] || {}, object)

مثال :

let myVar = {}
setPath(myVar, 'a.b.c', 42) => 42
console.log(myVar) => {a: {b: {c: 42}}}

دسترسی به آرایه با []:

const resolvePath = (object, path, defaultValue) => path
   .split(/[\.\[\]\'\"]/)
   .filter(p => p)
   .reduce((o, p) => o ? o[p] : defaultValue, object)

مثال :

const myVar = {a:{b:[{c:1}]}}
resolvePath(myVar,'a.b[0].c') => 1
resolvePath(myVar,'a["b"][\'0\'].c') => 1

پاسخ شماره ۵

شما باید رشته را خودتان تجزیه کنید:

function getProperty(obj, prop) {
    var parts = prop.split('.');

    if (Array.isArray(parts)) {
        var last = parts.pop(),
        l = parts.length,
        i = 1,
        current = parts[0];

        while((obj = obj[current]) && i < l) {
            current = parts[i];
            i++;
        }

        if(obj) {
            return obj[last];
        }
    } else {
        throw 'parts is not valid array';
    }
}

این نیاز داشت که شما همچنین ایندکسهای آرایه را با نشانهگذاری نقطهای تعریف کنید:

var part3name1 = "part3.0.name";

این پردازش را آسانتر میکند.

نسخه آزمایشی

پاسخ شماره ۶

برای آرایهها/آرایههای داخل شیء نیز کار میکند. در برابر مقادیر نامعتبر محافظتی دارد.

/**
 * Retrieve nested item from object/array
 * @param {Object|Array} obj
 * @param {String} path dot separated
 * @param {*} def default value ( if result undefined )
 * @returns {*}
 */
function path(obj, path, def){
    var i, len;

    for(i = 0,path = path.split('.'), len = path.length; i < len; i++){
        if(!obj || typeof obj !== 'object') return def;
        obj = obj[path[i]];
    }

    if(obj === undefined) return def;
    return obj;
}

//////////////////////////
//         TEST         //
//////////////////////////

var arr = [true, {'sp ace': true}, true]

var obj = {
  'sp ace': true,
  arr: arr,
  nested: {'dotted.str.ing': true},
  arr3: arr
}

shouldThrow(`path(obj, "arr.0")`);
shouldBeDefined(`path(obj, "arr[0]")`);
shouldBeEqualToNumber(`path(obj, "arr.length")`, 3);
shouldBeTrue(`path(obj, "sp ace")`);
shouldBeEqualToString(`path(obj, "none.existed.prop", "fallback")`, "fallback");
shouldBeTrue(`path(obj, "nested['dotted.str.ing'])`);
<script src="https://cdn.rawgit.com/coderek/e7b30bac7634a50ad8fd/raw/174b6634c8f57aa8aac0716c5b7b2a7098e03584/js-test.js"></script>

پاسخ شماره ۷

این احتمالاً هرگز منتشر نخواهد شد... اما به هر حال اینجا است.

  1. جایگزین کردن نحو براکت [] با .
  2. با کاراکتر . تقسیم کنید
  3. حذف رشتههای خالی
  4. مسیر را پیدا کنید (در غیر این صورت تعریفنشده)

(برای پیدا کردن مسیر به یک شی، از این راهحل pathTo استفاده کنید.)

// "one liner" (ES6)

const deep_value = (obj, path) => 
path
    .replace(/\[|\]\.?/g, '.')
    .split('.')
    .filter(s => s)
    .reduce((acc, val) => acc && acc[val], obj);
    
// ... and that's it.

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'
        }
        // ...
    ],
    'pa[rt3' : [
        {
            'name': 'Part 3A',
            'size': '10',
            'qty' : '20'
        }
        // ...
    ]
};

console.log(deep_value(someObject, "part1.name"));               // Part 1
console.log(deep_value(someObject, "part2.qty"));                // 60
console.log(deep_value(someObject, "part3[0].name"));            // Part 3A
console.log(deep_value(someObject, "part3[0].....name"));        // Part 3A - invalid blank paths removed
console.log(deep_value(someObject, "pa[rt3[0].name"));           // undefined - name does not support square brackets

پاسخ شماره ۸

استفاده از eval:

var part1name = eval("someObject.part1.name");

پیچیدن برای بازگرداندن مقدار تعریفنشده در صورت خطا

function path(obj, path) {
    try {
        return eval("obj." + path);
    } catch(e) {
        return undefined;
    }
}

http://jsfiddle.net/shanimal/b3xTw/

لطفاً هنگام استفاده از قدرت eval از عقل سلیم و احتیاط استفاده کنید. این کمی شبیه شمشیر لیزری است، اگر آن را روشن کنید، ۹۰٪ احتمال دارد یک عضو بدن را قطع کنید. این ابزار برای همه مناسب نیست.

پاسخ شماره ۹

شما میتوانید با استفاده از نشانهگذاری نقطهای مقدار یک عضو شیء عمیق را بدون هیچ کتابخانه خارجی جاوااسکریپت با ترفند ساده زیر به دست آورید:

function objectGet(obj, path) { return new Function('_', 'return _.' + path)(obj); };

در مورد شما برای دریافت مقدار part1.name از someObject کافی است که این کار را انجام دهید:

objectGet(someObject, 'part1.name');

در اینجا یک نمونه ساده با Fiddle آمده است: https://jsfiddle.net/harishanchu/oq5esowf/

پاسخ شماره ۱۰

این یک خط کد با lodash است.

const deep = { l1: { l2: { l3: "Hello" } } };
const prop = "l1.l2.l3";
const val = _.reduce(prop.split('.'), function(result, value) { return result ? result[value] : undefined; }, deep);
// val === "Hello"

یا حتی بهتر...

const val = _.get(deep, prop);

یا نسخه ES6 با reduce...

const val = prop.split('.').reduce((r, val) => { return r ? r[val] : undefined; }, deep);

پلانکر

پاسخ شماره ۱۱

فکر میکنم شما دارید این را میپرسید:

var part1name = someObject.part1.name;
var part2quantity = someObject.part2.qty;
var part3name1 =  someObject.part3[0].name;

ممکن است شما این را درخواست کنید:

var part1name = someObject["part1"]["name"];
var part2quantity = someObject["part2"]["qty"];
var part3name1 =  someObject["part3"][0]["name"];

هر دو کار خواهند کرد

یا شاید شما برای این درخواست میکنید

var partName = "part1";
var nameStr = "name";

var part1name = someObject[partName][nameStr];

در نهایت ممکن است شما درخواست چنین چیزی را داشته باشید

var partName = "part1.name";

var partBits = partName.split(".");

var part1name = someObject[partBits[0]][partBits[1]];

پاسخ شماره ۱۲

به جای تلاش برای تقلید از نحو JS که مجبور خواهید بود مقدار زیادی محاسبات برای تجزیه آن انجام دهید، یا فقط اشتباه کنید/چیزهایی را فراموش کنید مانند تعدادی از این پاسخها (کلیدهایی با . در آنها، کسی نیست؟)، فقط از یک آرایه از کلیدها استفاده کنید.

var part1name     = Object.get(someObject, ['part1', 'name']);
var part2quantity = Object.get(someObject, ['part2', 'qty']);
var part3name1    = Object.get(someObject, ['part3', 0, 'name']);

اگر به جای آن نیاز دارید از یک رشته واحد استفاده کنید، به سادگی آن را به JSON تبدیل کنید.
بهبود دیگری در این روش این است که میتوانید شیء سطح ریشه را حذف یا تنظیم کنید.

function resolve(obj, path) {
    let root = obj = [obj];
    path = [0, ...path];
    while (path.length > 1)
        obj = obj[path.shift()];
    return [obj, path[0], root];
}
Object.get = (obj, path) => {
    let [parent, key] = resolve(obj, path);
    return parent[key];
};
Object.del = (obj, path) => {
    let [parent, key, root] = resolve(obj, path);
    delete parent[key];
    return root[0];
};
Object.set = (obj, path, value) => {
    let [parent, key, root] = resolve(obj, path);
    parent[key] = value;
    return root[0];
};

نمایش سایر ویژگیها:

bob= برای .set(/.del( لازم نیست مگر اینکه مسیر شما ممکن است خالی باشد (دستکاری شی ریشه).
من ثابت میکنم که شی را کلون نمیکنم با استفاده از steve برای نگه داشتن ارجاع به شی اصلی و بررسی bob == steve //true بعد از آن اولین .set(

پاسخ شماره ۱۳

فقط در صورتی که کسی در سال ۲۰۱۷ یا بعد از آن به این سؤال سر میزند و به دنبال یک روش آسان برای به خاطر سپردن است، این یک پست وبلاگی مفصل در مورد دسترسی به اشیاء تو در تو در جاوااسکریپت بدون اینکه گیج شوید، است

خطای نمیتوان مشخصه 'foo' را از undefined خواند

دسترسی به اشیاء تو در تو با استفاده از تابع reduce آرایه

بیایید این ساختار نمونه را بررسی کنیم

const user = {
    id: 101,
    email: '[email ',
    personalInfo: {
        name: 'Jack',
        address: [{
            line1: 'westwish st',
            line2: 'washmasher',
            city: 'wallas',
            state: 'WX'
        }]
    }
}

برای اینکه بتوانید به آرایههای تو در تو دسترسی پیدا کنید، میتوانید ابزار reduce آرایه خود را بنویسید.

const getNestedObject = (nestedObj, pathArr) => {
    return pathArr.reduce((obj, key) =>
        (obj && obj[key] !== undefined) ? obj[key] : undefined, nestedObj);
}

// pass in your object structure as array elements
const name = getNestedObject(user, ['personalInfo', 'name']);

// to access nested array, just pass in array index as an element the path array.
const city = getNestedObject(user, ['personalInfo', 'address', 0, 'city']);
// this will return the city from the first address item.

همچنین یک کتابخانه عالی و کوچک برای مدیریت نوع به نام typy وجود دارد که همه این کارها را برای شما انجام میدهد.

با typy، کد شما به این شکل خواهد بود

const city = t(user, 'personalInfo.address[0].city').safeObject;

سلب مسئولیت: من نویسنده این بسته هستم.