簡體   English   中英

通過字符串路徑訪問嵌套的 JavaScript 對象和 arrays

[英]Accessing nested JavaScript objects and arrays by string path

我有一個這樣的數據結構:

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”。 與填充 60 的 part2quantity 相同。

無論如何用純 javascript 或 JQuery 來實現這一點?

我只是根據我已經擁有的一些類似代碼制作了這個,它似乎可以工作:

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 && 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 :Vanila 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}}})

對於一個即用型函數,它也可以識別 false、0 和負數並接受默認值作為參數:

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>

使用評估:

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 的力量時,請使用常識並謹慎行事。 這有點像一把光劍,如果你打開它,你有 90% 的機會會斷肢。 它不適合所有人。

這可能永遠不會看到曙光......但無論如何它就在這里。

  1. []括號語法替換為.
  2. 拆分在. 特點
  3. 刪除空白字符串
  4. 找到路徑(否則undefined

(要查找對象的路徑,請使用此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

您可以通過以下簡單技巧設法使用點表示法獲取深層對象成員的值,而無需任何外部 JavaScript 庫:

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

在您的情況下,要從someObject獲取part1.name的值,只需執行以下操作:

objectGet(someObject, 'part1.name');

這是一個簡單的小提琴演示: 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 版本...

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

Plunkr

我想你在問這個:

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

以防萬一,任何人在 2017 年或以后訪問這個問題並尋找一種易於記憶的方式,這里有一篇關於在 JavaScript 中訪問嵌套對象而不被迷惑的詳細博客文章

無法讀取未定義錯誤的屬性“foo”

使用數組歸約訪問嵌套對象

讓我們以這個示例結構為例

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

為了能夠訪問嵌套數組,您可以編寫自己的數組 reduce util。

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,您的代碼將如下所示

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

免責聲明:我是這個包的作者。

與其嘗試模擬 JS 語法,否則您將不得不花費大量計算解析,或者只是弄錯/忘記一堆這些答案(帶有. s 的鍵,有人嗎?),只需使用一組鍵。

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

回答

如果您需要使用單個字符串,只需 JSONify 即可。
此方法的另一個改進是您可以刪除/設置根級別對象。

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 = for .set( / .del(不是必需的。
我證明我沒有通過使用steve來保持對原始對象的引用並在第一個bob == steve //true .set(

在這里,我提供了更多方法,在許多方面看起來更快:

選項 1:在 . 或 [ 或 ] 或 ' 或 ",反轉它,跳過空項目。

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var parts = path.split(/\[|\]|\.|'|"/g).reverse(), name; // (why reverse? because it's usually faster to pop off the end of an array)
    while (parts.length) { name=parts.pop(); if (name) origin=origin[name]; }
    return origin;
}

選項 2(最快,除了eval ):低級字符掃描(沒有正則表達式/拆分/等,只是一個快速的字符掃描)。 注意:這個不支持索引的引號。

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var c = '', pc, i = 0, n = path.length, name = '';
    if (n) while (i<=n) ((c = path[i++]) == '.' || c == '[' || c == ']' || c == void 0) ? (name?(origin = origin[name], name = ''):(pc=='.'||pc=='['||pc==']'&&c==']'?i=n+2:void 0),pc=c) : name += c;
    if (i==n+2) throw "Invalid path: "+path;
    return origin;
} // (around 1,000,000+/- ops/sec)

選項 3:(:選項 2 擴展為支持引號 - 有點慢,但仍然很快)

function getValue(path, origin) {
    if (origin === void 0 || origin === null) origin = self ? self : this;
    if (typeof path !== 'string') path = '' + path;
    var c, pc, i = 0, n = path.length, name = '', q;
    while (i<=n)
        ((c = path[i++]) == '.' || c == '[' || c == ']' || c == "'" || c == '"' || c == void 0) ? (c==q&&path[i]==']'?q='':q?name+=c:name?(origin?origin=origin[name]:i=n+2,name='') : (pc=='['&&(c=='"'||c=="'")?q=c:pc=='.'||pc=='['||pc==']'&&c==']'||pc=='"'||pc=="'"?i=n+2:void 0), pc=c) : name += c;
    if (i==n+2 || name) throw "Invalid path: "+path;
    return origin;
}

JSPerf:http: //jsperf.com/ways-to-dereference-a-delimited-property-string/3

“eval(...)” 仍然是王者(即性能方面)。 如果您直接控制了屬性路徑,則使用“eval”應該沒有任何問題(尤其是在需要速度的情況下)。 如果將屬性路徑“拉過電線”(在線!?大聲笑:P),那么是的,使用其他東西是安全的。 只有白痴才會說永遠不要使用“eval”,因為有充分的理由何時使用它。 此外,“它用於Doug Crockford 的 JSON 解析器。” 如果輸入是安全的,那么就沒有問題了。 為正確的工作使用正確的工具,就是這樣。

AngularJS

Speigg 的方法非常簡潔明了,盡管我在搜索通過字符串路徑訪問 AngularJS $scope 屬性的解決方案時發現了這個回復,並且稍加修改就可以完成這項工作:

$scope.resolve = function( path, obj ) {
    return path.split('.').reduce( function( prev, curr ) {
        return prev[curr];
    }, obj || this );
}

只需將此函數放在您的根控制器中,然后在任何子范圍內使用它,如下所示:

$scope.resolve( 'path.to.any.object.in.scope')
/**
 * Access a deep value inside a object 
 * Works by passing a path like "foo.bar", also works with nested arrays like "foo[0][1].baz"
 * @author Victor B. https://gist.github.com/victornpb/4c7882c1b9d36292308e
 * Unit tests: http://jsfiddle.net/Victornpb/0u1qygrh/
 */
function getDeepVal(obj, path) {
    if (typeof obj === "undefined" || obj === null) return;
    path = path.split(/[\.\[\]\"\']{1,2}/);
    for (var i = 0, l = path.length; i < l; i++) {
        if (path[i] === "") continue;
        obj = obj[path[i]];
        if (typeof obj === "undefined" || obj === null) return;
    }
    return obj;
}

適用於

getDeepVal(obj,'foo.bar')
getDeepVal(obj,'foo.1.bar')
getDeepVal(obj,'foo[0].baz')
getDeepVal(obj,'foo[1][2]')
getDeepVal(obj,"foo['bar'].baz")
getDeepVal(obj,"foo['bar']['baz']")
getDeepVal(obj,"foo.bar.0.baz[1]['2']['w'].aaa[\"f\"].bb")

如果您想要一個可以正確檢測和報告路徑解析任何問題的詳細信息的解決方案,我為此編寫了自己的解決方案 - library path-value

const {resolveValue} = require('path-value');

resolveValue(someObject, 'part1.name'); //=> Part 1
resolveValue(someObject, 'part2.qty'); //=> 50
resolveValue(someObject, 'part3.0.name'); //=> Part 3A

請注意,對於索引,我們使用.0而不是[0] ,因為解析后者會增加性能損失,而.0直接在 JavaScript 中工作,因此非常快。

然而,也支持完整的 ES5 JavaScript 語法,它只需要首先被標記化:

const {resolveValue, tokenizePath} = require('path-value');

const path = tokenizePath('part3[0].name'); //=> ['part3', '0', 'name']

resolveValue(someObject, path); //=> Part 3A

我還沒有找到一個包來使用字符串路徑執行所有操作,所以我最終編寫了自己的快速小包,它支持 insert()、get()(默認返回)、set() 和 remove( ) 操作。

您可以使用點表示法、方括號、數字索引、字符串數字屬性和非單詞字符的鍵。 下面的簡單用法:

> var jsocrud = require('jsocrud');

...

// Get (Read) ---
> var obj = {
>     foo: [
>         {
>             'key w/ non-word chars': 'bar'
>         }
>     ]
> };
undefined

> jsocrud.get(obj, '.foo[0]["key w/ non-word chars"]');
'bar'

https://www.npmjs.com/package/jsocrud

https://github.com/vertical-knowledge/jsocrud

簡單的函數,允許字符串或數組路徑。

function get(obj, path) {
  if(typeof path === 'string') path = path.split('.');

  if(path.length === 0) return obj;
  return get(obj[path[0]], path.slice(1));
}

const obj = {a: {b: {c: 'foo'}}};

console.log(get(obj, 'a.b.c')); //foo

或者

console.log(get(obj, ['a', 'b', 'c'])); //foo

現在有一個npm模塊可以執行此操作: https ://github.com/erictrinh/safe-access

示例用法:

var access = require('safe-access');
access(very, 'nested.property.and.array[0]');

雖然 reduce 很好,但我很驚訝沒有人使用 forEach:

function valueForKeyPath(obj, path){
        const keys = path.split('.');
        keys.forEach((key)=> obj = obj[key]);
        return obj;
    };

測試

我正在使用 React 開發在線商店。 我試圖更改復制狀態對象中的值以在提交時使用它更新原始狀態。 上面的例子對我不起作用,因為它們中的大多數改變了復制對象的結構。 我找到了用於訪問和更改深層嵌套對象屬性值的函數的工作示例: https ://lowrey.me/create-an-object-by-path-in-javascript-2/ 這里是:

const createPath = (obj, path, value = null) => {
  path = typeof path === 'string' ? path.split('.') : path;
  let current = obj;
  while (path.length > 1) {
    const [head, ...tail] = path;
    path = tail;
    if (current[head] === undefined) {
      current[head] = {};
    }
    current = current[head];
  }
  current[path[0]] = value;
  return obj;
};

最近剛剛有同樣的問題並成功使用了https://npmjs.org/package/tea-properties ,它也set了嵌套對象/數組:

得到:

var o = {
  prop: {
    arr: [
      {foo: 'bar'}
    ]
  }
};

var properties = require('tea-properties');
var value = properties.get(o, 'prop.arr[0].foo');

assert(value, 'bar'); // true

放:

var o = {};

var properties = require('tea-properties');
properties.set(o, 'prop.arr[0].foo', 'bar');

assert(o.prop.arr[0].foo, 'bar'); // true

根據之前的回答,我創建了一個也可以處理括號的函數。 但由於分裂,它們內部沒有點。

function get(obj, str) {
  return str.split(/\.|\[/g).map(function(crumb) {
    return crumb.replace(/\]$/, '').trim().replace(/^(["'])((?:(?!\1)[^\\]|\\.)*?)\1$/, (match, quote, str) => str.replace(/\\(\\)?/g, "$1"));
  }).reduce(function(obj, prop) {
    return obj ? obj[prop] : undefined;
  }, obj);
}

 // (IE9+) Two steps var pathString = "[0]['property'].others[3].next['final']"; var obj = [{ property: { others: [1, 2, 3, { next: { final: "SUCCESS" } }] } }]; // Turn string to path array var pathArray = pathString .replace(/\[["']?([\w]+)["']?\]/g,".$1") .split(".") .splice(1); // Add object prototype method Object.prototype.path = function (path) { try { return [this].concat(path).reduce(function (f, l) { return f[l]; }); } catch (e) { console.error(e); } }; // usage console.log(obj.path(pathArray)); console.log(obj.path([0,"doesNotExist"]));

受@webjay 回答的啟發: https ://stackoverflow.com/a/46008856/4110122

我做了這個函數,你可以用它來獲取/設置/取消設置對象中的任何值

function Object_Manager(obj, Path, value, Action) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

要使用它:

 // Set
 Object_Manager(Obj,[Level1,Level2,Level3],New_Value, 'Set');

 // Get
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Get');

 // Unset
 Object_Manager(Obj,[Level1,Level2,Level3],'', 'Unset');

Mohamad Hamouday' Answer 的擴展將填補缺失的密鑰

function Object_Manager(obj, Path, value, Action, strict) 
{
    try
    {
        if(Array.isArray(Path) == false)
        {
            Path = [Path];
        }

        let level = 0;
        var Return_Value;
        Path.reduce((a, b)=>{
            console.log(level,':',a, '|||',b)
            if (!strict){
              if (!(b in a)) a[b] = {}
            }


            level++;
            if (level === Path.length)
            {
                if(Action === 'Set')
                {
                    a[b] = value;
                    return value;
                }
                else if(Action === 'Get')
                {
                    Return_Value = a[b];
                }
                else if(Action === 'Unset')
                {
                    delete a[b];
                }
            } 
            else 
            {
                return a[b];
            }
        }, obj);
        return Return_Value;
    }

    catch(err)
    {
        console.error(err);
        return obj;
    }
}

例子


obja = {
  "a": {
    "b":"nom"
  }
}

// Set
path = "c.b" // Path does not exist
Object_Manager(obja,path.split('.'), 'test_new_val', 'Set', false);

// Expected Output: Object { a: Object { b: "nom" }, c: Object { b: "test_new_value" } }

您可以使用ramda庫。

學習ramda還可以幫助您輕松地使用不可變對象。


var obj = {
  a:{
    b: {
      c:[100,101,{
        d: 1000
      }]
    }
  }
};


var lens = R.lensPath('a.b.c.2.d'.split('.'));
var result = R.view(lens, obj);


https://codepen.io/ghominejad/pen/BayJZOQ

基於 Alnitak 的回答

我將 polyfill 包裹在支票中,並將函數簡化為單個鏈式縮減。

 if (Object.byPath === undefined) { Object.byPath = (obj, path) => path .replace(/\[(\w+)\]/g, '.$1') .replace(/^\./, '') .split(/\./g) .reduce((ref, key) => key in ref ? ref[key] : ref, obj) } const data = { foo: { bar: [{ baz: 1 }] } } console.log(Object.byPath(data, 'foo.bar[0].baz'))

這可以通過將邏輯拆分為三個獨立的函數來簡化:

const isVal = a => a != null; // everything except undefined + null

const prop = prop => obj => {
    if (isVal(obj)) {
        const value = obj[prop];
        if (isVal(value)) return value;
        else return undefined;
    } else return undefined;
};

const path = paths => obj => {
    const pathList = typeof paths === 'string' ? paths.split('.') : paths;
    return pathList.reduce((value, key) => prop(key)(value), obj);
};

//usage:
const myObject = { foo: { bar: { baz: 'taco' } } };
const result = path('foo.bar')(myObject);
//results => { baz: 'taco' }

此變體支持:

  • 傳遞數組或字符串參數
  • 在調用和執行期間處理undefined的值
  • 獨立測試每個功能
  • 獨立使用每個功能

這個解決方案怎么樣:

setJsonValue: function (json, field, val) {
  if (field !== undefined){
    try {
      eval("json." + field + " = val");
    }
    catch(e){
      ;
    }
  }  
}

而這個,用於獲得:

getJsonValue: function (json, field){
  var value = undefined;
  if (field !== undefined) {
    try {
      eval("value = json." + field);
    } 
    catch(e){
      ;
    }
  }
  return value;
};

可能有些人會認為它們不安全,但它們解析字符串時必須快得多。

這里的解決方案僅用於訪問深度嵌套的鍵。 我需要一個來訪問、添加、修改和刪除密鑰。 這就是我想出的:

var deepAccessObject = function(object, path_to_key, type_of_function, value){
    switch(type_of_function){
        //Add key/modify key
        case 0: 
            if(path_to_key.length === 1){
                if(value)
                    object[path_to_key[0]] = value;
                return object[path_to_key[0]];
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    object[path_to_key[0]] = {};
            }
            break;
        //delete key
        case 1:
            if(path_to_key.length === 1){
                delete object[path_to_key[0]];
                return true;
            }else{
                if(object[path_to_key[0]])
                    return deepAccessObject(object[path_to_key[0]], path_to_key.slice(1), type_of_function, value);
                else
                    return false;
            }
            break;
        default:
            console.log("Wrong type of function");
    }
};
  • path_to_key :數組中的路徑。 您可以用您的string_path.split(".")替換它。
  • type_of_function : 0 用於訪問(不要將任何值傳遞給value ),0 用於添加和修改。 1 用於刪除。

基於 Alnitak 的回答:

if(!Object.prototype.byString){
  //NEW byString which can update values
Object.prototype.byString = function(s, v, o) {
  var _o = o || this;
      s = s.replace(/\[(\w+)\]/g, '.$1'); // CONVERT INDEXES TO PROPERTIES
      s = s.replace(/^\./, ''); // STRIP A LEADING DOT
      var a = s.split('.'); //ARRAY OF STRINGS SPLIT BY '.'
      for (var i = 0; i < a.length; ++i) {//LOOP OVER ARRAY OF STRINGS
          var k = a[i];
          if (k in _o) {//LOOP THROUGH OBJECT KEYS
              if(_o.hasOwnProperty(k)){//USE ONLY KEYS WE CREATED
                if(v !== undefined){//IF WE HAVE A NEW VALUE PARAM
                  if(i === a.length -1){//IF IT'S THE LAST IN THE ARRAY
                    _o[k] = v;
                  }
                }
                _o = _o[k];//NO NEW VALUE SO JUST RETURN THE CURRENT VALUE
              }
          } else {
              return;
          }
      }
      return _o;
  };

}

這也允許您設置一個值!

我也用這個創建了一個npm 包github

可以使用數組代替字符串來處理嵌套對象和數組,例如: ["my_field", "another_field", 0, "last_field", 10]

這是一個基於此數組表示更改字段的示例。 我在 react.js 中使用類似的東西來改變嵌套結構的狀態的受控輸入字段。

let state = {
        test: "test_value",
        nested: {
            level1: "level1 value"
        },
        arr: [1, 2, 3],
        nested_arr: {
            arr: ["buh", "bah", "foo"]
        }
    }

function handleChange(value, fields) {
    let update_field = state;
    for(var i = 0; i < fields.length - 1; i++){
        update_field = update_field[fields[i]];
    }
    update_field[fields[fields.length-1]] = value;
}

handleChange("update", ["test"]);
handleChange("update_nested", ["nested","level1"]);
handleChange(100, ["arr",0]);
handleChange('changed_foo', ["nested_arr", "arr", 3]);
console.log(state);

使用UnderscorepropertypropertyOf

 var test = { foo: { bar: { baz: 'hello' } } } var string = 'foo.bar.baz'; // document.write(_.propertyOf(test)(string.split('.'))) document.write(_.property(string.split('.'))(test));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>

祝你好運...

React 示例 - 使用 lodash

從性能的角度來看,這可能不是最有效的方法,但如果您的應用程序是單體應用程序,它肯定會為您節省一些時間。 尤其是當您將狀態數據格式與 API 后端緊密耦合時。

   import set from "lodash/set";  // More efficient import

    class UserProfile extends Component {

      constructor(props){
        super(props);

        this.state = {
          user: {
            account: {
              id: "",
              email: "",
              first_name: ""
            }
          }
        }
      }

       /**
       * Updates the state based on the form input
       * 
       * @param {FormUpdate} event 
       */
      userAccountFormHook(event) {
        // https://lodash.com/docs#get
        // https://lodash.com/docs#set
        const { name, value } = event.target;
        let current_state = this.state
        set(current_state, name, value)  // Magic happens here
        this.setState(current_state);
      }

    render() {
        return (
          <CustomFormInput
            label: "First Name"
            type: "text"
            placeholder: "First Name"
            name: "user.account.first_name"
            onChange: {this.userAccountFormHook}
            value: {this.state.user.account.first_name}

          />
      )
  }
}

AngularJS 有$scope.$eval

使用 AngularJS,可以使用$scope.$eval方法來訪問嵌套對象:

$scope.someObject = someObject;
console.log( $scope.$eval("someObject.part3[0].name") ); //Part 3A

有關詳細信息,請參閱

演示

 angular.module("app",[]) .run(function($rootScope) { $rootScope.someObject = { 'part2' : { 'name': 'Part 2', 'size': '15', 'qty' : '60' }, 'part3' : [{ 'name': 'Part 3A', 'size': '10', 'qty' : '20' },{ name: 'Part 3B' }] }; console.log( "part3[0].name =", $rootScope.$eval("someObject.part3[0].name") ); })
 <script src="//unpkg.com/angular/angular.js"></script> <body ng-app="app" </body>

請注意,以下內容不適用於所有有效的 unicode 屬性名稱(但據我所知,其他任何答案都不會)。

 const PATTERN = /[\^|\[|\.]([$|\w]+)/gu function propValue(o, s) { const names = [] for(let [, name] of [...s.matchAll(PATTERN)]) names.push(name) return names.reduce((p, propName) => { if(!p.hasOwnProperty(propName)) throw 'invalid property name' return p[propName] }, o) } let path = 'myObject.1._property2[0][0].$property3' let o = { 1: { _property2: [ [{ $property3: 'Hello World' }] ] } } console.log(propValue(o, path)) // 'Hello World'

如果您需要在編碼時訪問不同的嵌套鍵而不知道它(解決它們很簡單),您可以使用數組表示法訪問器:

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

它們等效於點符號訪問器,並且可能在運行時有所不同,例如:

var part = 'part1';
var property = 'name';

var part1name = someObject[part][property];

相當於

var part1name = someObject['part1']['name'];

或者

var part1name = someObject.part1.name;

我希望這能解決你的問題......

編輯

我不會使用字符串來維護某種 xpath查詢來訪問對象值。 由於您必須調用一個函數來解析查詢並檢索值,因此我將遵循另一條路徑(不是:

var part1name = function(){ return this.part1.name; }
var part2quantity = function() { return this['part2']['qty']; }
var part3name1 =  function() { return this.part3[0]['name'];}

// usage: part1name.apply(someObject);

或者,如果您對apply方法感到不安

var part1name = function(obj){ return obj.part1.name; }
var part2quantity = function(obj) { return obj['part2']['qty']; }
var part3name1 =  function(obj) { return obj.part3[0]['name'];}

// usage: part1name(someObject);

函數更短、更清晰,解釋器會為您檢查它們的語法錯誤等等。

順便說一句,我覺得在正確的時間做一個簡單的任務就足夠了......

使用對象掃描,這變成了一條線。 然而更重要的是,該解決方案考慮了性能:

  • 搜索過程中遍歷了一次輸入(即使查詢了多個鍵)
  • 解析僅在初始化時發生一次(以防查詢多個對象)
  • 允許使用*擴展語法

 // const objectScan = require('object-scan'); const 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' }] }; const get = (haystack, needle) => objectScan([needle], { rtn: 'value', abort: true })(haystack); console.log(get(someObject, 'part1.name')); // => Part 1 console.log(get(someObject, 'part2.qty')); // => 60 console.log(get(someObject, 'part3[0].name')); // => Part 3A const getAll = (haystack, ...needles) => objectScan(needles, { reverse: false, rtn: 'entry', joined: true })(haystack); console.log(getAll(someObject, 'part1.name', 'part2.qty', 'part3[0].name')); /* => [ [ 'part1.name', 'Part 1' ], [ 'part2.qty', '60' ], [ 'part3[0].name', 'Part 3A' ] ] */ console.log(getAll(someObject, 'part1.*')); /* => [ [ 'part1.name', 'Part 1' ], [ 'part1.size', '20' ], [ 'part1.qty', '50' ] ] */
 .as-console-wrapper {max-height: 100% !important; top: 0}
 <script src="https://bundle.run/object-scan@13.8.0"></script>

免責聲明:我是對象掃描的作者

我的解決方案基於@AdrianoSpadoni 給出的解決方案,並解決了克隆對象的需要

function generateData(object: any, path: string, value: any): object {
  const clone = JSON.parse(JSON.stringify(object));
  path
    .split(".")
    .reduce(
    (o, p, i) => (o[p] = path.split(".").length === ++i ? value : o[p] || {}),
  clone
);
  return clone;
}

我查看了所有其他答案,決定將改進添加到更具可讀性的代碼中:

function getObjectValByString(obj, str) {
if (typeof obj === "string") return obj;

const fields = str.split(".");

return getObjectValByString(obj[fields[0]], fields.slice(1).join("."));}

這是一個代碼片段:

 let someObject = { partner: { id: "AIM", person: { name: "ANT", an: { name: "ESM" }, }, }, }; function getObjectValByString(obj, str) { if (typeof obj === "string") return obj; const fields = str.split("."); return getObjectValByString(obj[fields[0]], fields.slice(1).join(".")); } const result = getObjectValByString(someObject, "partner.person.an.name"); console.log({ result, });

 DotObject = obj => new Proxy(obj, { get: function (o,k) { let m = k.match(/(.+?)\.(.+)/) return m ? this.get(o[m[1]], m[2]) : o[k] } }) let test = DotObject({a: {b: {c: 'wow'}}}) console.log(test['abc'])

從@Alnitak answer開始,我構建了這個源,它下載一個實際的 .JSON 文件並對其進行處理,為每個步驟打印到控制台解釋性字符串,並在傳遞錯誤鍵的情況下提供更多詳細信息:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  <script>
function retrieveURL(url) {
        var client = new XMLHttpRequest();
        prefix = "https://cors-anywhere.herokuapp.com/"
        client.open('GET', prefix + url);
        client.responseType = 'text';
        client.onload = function() {
            response = client.response; // Load remote response.
            console.log("Response received.");
            parsedJSON  = JSON.parse(response);
            console.log(parsedJSON);
            console.log(JSONitemByPath(parsedJSON,"geometry[6].obs[3].latituade"));
            return response;
        };
        try {
            client.send();
        } catch(e) {
            console.log("NETWORK ERROR!");
            console.log(e);
        }
}



function JSONitemByPath(o, s) {
    structure = "";
    originalString = s;
    console.log("Received string: ", s);
    s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
    console.log("Converted to   : ", s);
    s = s.replace(/^\./, '');           // strip a leading dot
    var a = s.split('.');

    console.log("Single keys to parse: ",a);

    for (var i = 0, n = a.length; i < n; ++i) {
        var k = a[i];
        if (k in o) {
            o = o[k];
            console.log("object." + structure +  a[i], o);
            structure +=  a[i] + ".";
        } else {
            console.log("ERROR: wrong path passed: ", originalString);
            console.log("       Last working level: ", structure.substr(0,structure.length-1));
            console.log("       Contents: ", o);
            console.log("       Available/passed key: ");
            Object.keys(o).forEach((prop)=> console.log("       "+prop +"/" + k));
            return;
        }
    }
    return o;
}


function main() {
    rawJSON = retrieveURL("http://haya2now.jp/data/data.json");
}

</script>
  </head>
  <body onload="main()">
  </body>
</html>

輸出示例:

Response received.
json-querier.html:17 {geometry: Array(7), error: Array(0), status: {…}}
json-querier.html:34 Received string:  geometry[6].obs[3].latituade
json-querier.html:36 Converted to   :  geometry.6.obs.3.latituade
json-querier.html:40 Single keys to parse:  (5) ["geometry", "6", "obs", "3", "latituade"]
json-querier.html:46 object.geometry (7) [{…}, {…}, {…}, {…}, {…}, {…}, {…}]
json-querier.html:46 object.geometry.6 {hayabusa2: {…}, earth: {…}, obs: Array(6), TT: 2458816.04973593, ryugu: {…}, …}
json-querier.html:46 object.geometry.6.obs (6) [{…}, {…}, {…}, {…}, {…}, {…}]
json-querier.html:46 object.geometry.6.obs.3 {longitude: 148.98, hayabusa2: {…}, sun: {…}, name: "DSS-43", latitude: -35.4, …}
json-querier.html:49 ERROR: wrong path passed:  geometry[6].obs[3].latituade
json-querier.html:50        Last working level:  geometry.6.obs.3
json-querier.html:51        Contents:  {longitude: 148.98, hayabusa2: {…}, sun: {…}, name: "DSS-43", latitude: -35.4, …}
json-querier.html:52        Available/passed key: 
json-querier.html:53        longitude/latituade
json-querier.html:53        hayabusa2/latituade
json-querier.html:53        sun/latituade
json-querier.html:53        name/latituade
json-querier.html:53        latitude/latituade
json-querier.html:53        altitude/latituade
json-querier.html:18 undefined

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM