ドット表記のJavaScript文字列をオブジェクト参照に変換します


214

JSオブジェクトを指定

var obj = { a: { b: '1', c: '2' } }

そしてひも

"a.b"

文字列をドット表記に変換するにはどうすればよいですか

var val = obj.a.b

文字列がちょうどだった場合は、'a'使用できますobj[a]。しかし、これはもっと複雑です。簡単な方法はあると思いますが、現在は脱出しています。


25
@アンドレイevalは悪です。使用しないでください
kevinji '18 / 06/18


perfが重要な考慮事項であり、同じパスを頻繁に再利用している場合(たとえば、配列フィルター関数内)、以下の私の回答で説明されているように関数コンストラクターを使用します。同じパスが何千回も使用される場合、Functionメソッドは、すべての逆参照でパスを評価または分割して削減するよりも10倍以上速くなる可能性があります。
Kevin Crumley、


回答:


437

最近のメモ:この回答が多くの賛成票を獲得したことを嬉しく思いますが、私も多少恐怖を感じます。「xabc」などのドット表記文字列を参照に変換する必要がある場合は、(おそらく、奇妙な逆シリアル化を実行している場合を除き)非常に問題が発生している可能性があります。

つまり、この答えへの道を見つけた初心者は、「なぜ私はこれをしているのですか?」

もちろん、ユースケースが小さく、パフォーマンスの問題が発生しない場合は、通常これで問題ありません。また、後で複雑にするために抽象化を構築する必要はありません。実際、これによりコードの複雑さが軽減され、物事がシンプルに保たれる場合は、おそらく先に進んで、OPが求めていることを実行する必要があります。ただし、そうでない場合は、以下のいずれかに当てはまるかどうかを検討してください。

ケース1:データを操作する主な方法として(たとえば、オブジェクトを渡したり、オブジェクトを逆参照したりするアプリのデフォルトの形式として)。「文字列から関数または変数の名前を調べるにはどうすればよいですか?」

  • これは不適切なプログラミング方法です(特に不要なメタプログラミングで、関数の副作用のないコーディングスタイルに違反し、パフォーマンスに影響します)。この場合、自分自身を見つけた初心者は、代わりに配列表現、たとえば['x'、 'a'、 'b'、 'c']を使用することを検討する必要があります。参照自体を最初に追跡する(クライアント側のみまたはサーバー側のみの場合が最も理想的)など(既存の一意のIDは追加に適さないが、仕様で別途必要な場合に使用できます)存在にかかわらず。)

ケース2:シリアル化されたデータ、またはユーザーに表示されるデータの操作。Dateオブジェクトではなく、日付を文字列 "1999-12-30"として使用するようなものです(注意しないと、タイムゾーンのバグが発生したり、シリアル化が複雑になったりする可能性があります)。またはあなたはあなたが何をしているのか知っています。

  • これは多分大丈夫です。ドット文字列 "。"がないことに注意してください。サニタイズされた入力フラグメント。

この答えを常に使用していて、文字列と配列の間で相互に変換していることに気付いた場合は、問題が発生している可能性があり、別の方法を検討する必要があります。

以下は、他のソリューションよりも10倍短いエレガントなワンライナーです。

function index(obj,i) {return obj[i]}
'a.b.etc'.split('.').reduce(index, obj)

[編集]またはECMAScript 6では:

'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)

(それにもかかわらず、これらの人々は、このメソッドはevalをを使用していないことを喜んでされるわけではありません、私は他の人はそれが普通ですが(それがであることを示唆しているようにeval常に悪いと思うこと)。上記のでしょうobj.a.b.etc与えられたobj文字列"a.b.etc"。)

reduceECMA-262標準(第5版)であるにもかかわらず、まだ使用を恐れている人たちに応えて、以下は2行の再帰的な実装です。

function multiIndex(obj,is) {  // obj,['1','2','3'] -> ((obj['1'])['2'])['3']
    return is.length ? multiIndex(obj[is[0]],is.slice(1)) : obj
}
function pathIndex(obj,is) {   // obj,'1.2.3' -> multiIndex(obj,['1','2','3'])
    return multiIndex(obj,is.split('.'))
}
pathIndex('a.b.etc')

JSコンパイラーが行っている最適化によっては、ネストされた関数が通常のメソッド(クロージャー、オブジェクト、またはグローバル名前空間に配置)を介した呼び出しごとに再定義されないようにする必要があります。

編集

コメントで興味深い質問に答えるには:

これをセッターにどのように変えますか?パスによって値を返すだけでなく、新しい値が関数に送信された場合に値を設定しますか?– Swader 6月28日21:42

(追記:それは呼び出し規則に違反するとして、悲しいことに、セッターを持つオブジェクトを返すことはできません。コメンターは、代わりのような副作用を持つ一般的なセッタースタイルの機能に言及しているように見えるindex(obj,"a.b.etc", value)ことobj.a.b.etc = value。)

reduceスタイルはそれに本当に適していませんが、我々は再帰的な実装を変更することができます。

function index(obj,is, value) {
    if (typeof is == 'string')
        return index(obj,is.split('.'), value);
    else if (is.length==1 && value!==undefined)
        return obj[is[0]] = value;
    else if (is.length==0)
        return obj;
    else
        return index(obj[is[0]],is.slice(1), value);
}

デモ:

> obj = {a:{b:{etc:5}}}

> index(obj,'a.b.etc')
5
> index(obj,['a','b','etc'])   #works with both strings and lists
5

> index(obj,'a.b.etc', 123)    #setter-mode - third argument (possibly poor form)
123

> index(obj,'a.b.etc')
123

...個人的には別の機能を作ることをお勧めしますsetIndex(...)。質問の元のposerは、.split文字列ではなく、インデックスの配列(から取得できます)で動作している可能性がある(すべきですか?)と付記しておきます。ただし、通常は便利な機能に問題はありません。


コメント者は尋ねました:

配列はどうですか?"ab [4] .cd [1] [2] [3]"のようなもの?–AlexS

JavaScriptは非常に奇妙な言語です。一般に、オブジェクトはプロパティキーとして文字列のみを持つことができます。たとえば、のxような一般的なオブジェクトである場合x={}、次のx[1]ようになりx["1"]ます。

JavaScript配列(それ自体がObjectのインスタンスです)は、のようなことができても、特に整数キーを推奨しますx=[]; x["puppy"]=5;

ただし、一般的には(例外もあります)、x["somestring"]===x.somestring(許可されている場合は実行できませんx.123)。

(使用しているJSコンパイラーが仕様に違反しないことを証明できる場合は、これらをより適切な表現にコンパイルするために選択する可能性があることを覚えておいてください。)

したがって、あなたの質問への答えは、それらのオブジェクトが整数のみを受け入れると想定しているかどうか(問題ドメインの制限のため)かどうかに依存します。そうではないと仮定しましょう。次に、有効な式は、ベース識別子といくつか.identifierのsといくつか["stringindex"]のsを連結したものです。

これはと同等ですがa["b"][4]["c"]["d"][1][2][3]、おそらくサポートする必要がありa.b["c\"validjsstringliteral"][3]ます。有効な文字列リテラルを解析する方法を確認するには、文字列リテラルのecmascript文法セクションを確認する必要があります。技術的には、(私の最初の回答とは異なり)a有効なJavaScript識別子であることも確認する必要があります。

ただし、文字列にコンマまたは角かっこが含まれていない場合、質問に対する単純な答えは、セット,または[orにない長さ1+の文字シーケンスに一致することです]

> "abc[4].c.def[1][2][\"gh\"]".match(/[^\]\[.]+/g)
// ^^^ ^  ^ ^^^ ^  ^   ^^^^^
["abc", "4", "c", "def", "1", "2", ""gh""]

文字列にエスケープ文字または"文字が含まれておらず、 IdentifierNamesがStringLiteralsのサブ言語であるため(私は思いますか???)、最初にドットを[]に変換できます。

> var R=[], demoString="abc[4].c.def[1][2][\"gh\"]";
> for(var match,matcher=/^([^\.\[]+)|\.([^\.\[]+)|\["([^"]+)"\]|\[(\d+)\]/g; 
      match=matcher.exec(demoString); ) {
  R.push(Array.from(match).slice(1).filter(x=>x!==undefined)[0]);
  // extremely bad code because js regexes are weird, don't use this
}
> R

["abc", "4", "c", "def", "1", "2", "gh"]

もちろん、常に注意し、データを信頼しないでください。これを行ういくつかの悪い方法は、いくつかのユースケースではうまくいくかもしれませんが、以下も含まれます:

// hackish/wrongish; preprocess your string into "a.b.4.c.d.1.2.3", e.g.: 
> yourstring.replace(/]/g,"").replace(/\[/g,".").split(".")
"a.b.4.c.d.1.2.3"  //use code from before

スペシャル2018編集:

フルサークルを行くと、私たちが思い付くことができます...構文の利益のために最も効率の悪い、恐ろしく-overmetaprogrammedソリューションましょう純度 hamfisteryを。ES6プロキシオブジェクトを使用して...!また、不適切に作成されたライブラリを破壊する可能性のあるいくつかのプロパティも定義しましょう(これらはすばらしいとは言えますがすばらしい)。パフォーマンス、正気度(自分のものか他人のものか)、自分の仕事などを気にする場合は、これを使用することに注意する必要があります。

// [1,2,3][-1]==3 (or just use .slice(-1)[0])
if (![1][-1])
    Object.defineProperty(Array.prototype, -1, {get() {return this[this.length-1]}}); //credit to caub

// WARNING: THIS XTREME™ RADICAL METHOD IS VERY INEFFICIENT,
// ESPECIALLY IF INDEXING INTO MULTIPLE OBJECTS,
// because you are constantly creating wrapper objects on-the-fly and,
// even worse, going through Proxy i.e. runtime ~reflection, which prevents
// compiler optimization

// Proxy handler to override obj[*]/obj.* and obj[*]=...
var hyperIndexProxyHandler = {
    get: function(obj,key, proxy) {
        return key.split('.').reduce((o,i)=>o[i], obj);
    },
    set: function(obj,key,value, proxy) {
        var keys = key.split('.');
        var beforeLast = keys.slice(0,-1).reduce((o,i)=>o[i], obj);
        beforeLast[keys[-1]] = value;
    },
    has: function(obj,key) {
        //etc
    }
};
function hyperIndexOf(target) {
    return new Proxy(target, hyperIndexProxyHandler);
}

デモ:

var obj = {a:{b:{c:1, d:2}}};
console.log("obj is:", JSON.stringify(obj));

var objHyper = hyperIndexOf(obj);
console.log("(proxy override get) objHyper['a.b.c'] is:", objHyper['a.b.c']);
objHyper['a.b.c'] = 3;
console.log("(proxy override set) objHyper['a.b.c']=3, now obj is:", JSON.stringify(obj));

console.log("(behind the scenes) objHyper is:", objHyper);

if (!({}).H)
    Object.defineProperties(Object.prototype, {
        H: {
            get: function() {
                return hyperIndexOf(this); // TODO:cache as a non-enumerable property for efficiency?
            }
        }
    });

console.log("(shortcut) obj.H['a.b.c']=4");
obj.H['a.b.c'] = 4;
console.log("(shortcut) obj.H['a.b.c'] is obj['a']['b']['c'] is", obj.H['a.b.c']);

出力:

objは次のとおりです:{"a":{"b":{"c":1、 "d":2}}}

(プロキシオーバーライドget)objHyper ['abc'] is:1

(プロキシオーバーライドセット)objHyper ['abc'] = 3、objは次のようになります:{"a":{"b":{"c":3、 "d":2}}}

(舞台裏)objHyperは次のとおりです:プロキシ{a:{…}}

(ショートカット)obj.H ['abc'] = 4

(ショートカット)obj.H ['abc'] is obj ['a'] ['b'] ['c'] is:4

非効率的なアイデア:入力引数に基づいてディスパッチするように上記を変更できます。.match(/[^\]\[.]+/g)メソッドを使用してをサポートするobj['keys'].like[3]['this']か、ifの場合instanceof Arrayは、配列をのように入力として受け入れますkeys = ['a','b','c']; obj.H[keys]


おそらく未定義のインデックスを「よりソフトな」NaNスタイルの方法で処理したいという提案(たとえばindex({a:{b:{c:...}}}, 'a.x.c')、キャッチされていないTypeErrorではなくundefinedを返す)...:

1)これは、1次元のインデックス状況({})['eg'] == undefinedで、「エラーをスローするのではなく、未定義を返す必要がある」という観点から理にかなっているので、「 N次元の状況では「エラー」。

2)これは、上記の例ではTypeErrorで失敗する、私たちが実行している観点からは意味をなしませx['a']['x']['c']

そうは言っても、あなたはあなたの還元関数を次のいずれかに置き換えることによってこれを機能させるでしょう:

(o,i)=>o===undefined?undefined:o[i]、または (o,i)=>(o||{})[i]

(forループを使用し、次にインデックスを作成するサブ結果が未定義の場合はいつでもブレーク/リターンするか、そのような失敗が十分に稀であると予想される場合はtry-catchを使用することで、これをより効率的にすることができます。)


4
reduce現在使用されているすべてのブラウザでサポートされているわけではありません。
Ricardo Tomasi

14
@Ricardo:Array.reduceECMA-262標準の一部です。古いブラウザを本当にサポートしたい場合は、Array.prototype.reduceどこかに指定されているサンプル実装を定義できます(例:developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…)。
ninjagecko

2
はい、でも2行を関数に入れるのは簡単です。 var setget = function( obj, path ){ function index( robj,i ) {return robj[i]}; return path.split('.').reduce( index, obj ); }
nevf

3
私はこのエレガントな例が大好きです、ninjageckoに感謝します。空の文字列だけでなく、配列スタイルの表記を処理するように拡張しました-ここの私の例を参照してください:jsfiddle.net/sc0ttyd/q7zyd
Sc0ttyD

2
@ninjageckoこれをセッターにする方法を教えてください。パスによって値を返すだけでなく、新しい値が関数に送信された場合に値を設定しますか?
Swader 2013年

64

lodashを使用できる場合は、それを行う関数があります。

_.get(オブジェクト、パス、[デフォルト値])

var val = _.get(obj, "a.b");

4
注:_.get(object, path)パスが見つからなかった場合でも壊れません。'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)します。私の特定のケースでは-すべてのケースではなく-まさに私が必要とするもの。ありがとう!
Mr. B.

1
@ Mr.B。Lodashの最新バージョンには、3番目のオプションの引数がありdefaultValueます。_.get()この方法は、以下の場合、デフォルト値を返します_.get()解決さにundefined、そう何でもあなたが設定した値のためにしたいと時計に設定します。
steampowered

7
疑問に思う方のために、それもサポートしています_.set(object, path, value)
Jeffrey Roosendaal 2017年

19

再帰のもう少し複雑な例。

function recompose(obj,string){
    var parts = string.split('.');
    var newObj = obj[parts[0]];
    if(parts[1]){
        parts.splice(0,1);
        var newString = parts.join('.');
        return recompose(newObj,newString);
    }
    return newObj;
}


var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

alert(recompose(obj,'a.d.a.b')); //blah

19

lodash.getを使用することもできます

このパッケージ(npm i --save lodash.get)をインストールして、次のように使用します。

const get = require('lodash.get');

const myObj = { user: { firstName: 'Stacky', lastName: 'Overflowy' }, id: 123 };

console.log(get(myObj, 'user.firstName')); // prints Stacky
console.log(get(myObj, 'id')); //prints  123

//You can also update values
get(myObj, 'user').firstName = John;

10

同じパスを何度も逆参照することが予想される場合、各ドット表記パスの関数を構築すると、実際にはるかに優れたパフォーマンスが得られます(上記のコメントでJames Wilkinsがリンクしているパフォーマンステストを拡張します)。

var path = 'a.b.x';
var getter = new Function("obj", "return obj." + path + ";");
getter(obj);

Functionコンストラクターの使用には、セキュリティと最悪の場合のパフォーマンスの点でeval()と同じ欠点がありますが、IMOは、極端なダイナミズムと高パフォーマンスの組み合わせが必要な場合に、あまり使用されないツールです。この方法を使用して配列フィルター関数を作成し、AngularJSダイジェストループ内で呼び出します。私のプロファイルでは、動的に定義された3〜4レベルの深さのパスを使用して、約2000の複雑なオブジェクトの逆参照とフィルタリングに1ms未満かかるarray.filter()ステップを一貫して示しています。

もちろん、同様の方法を使用してセッター関数を作成できます。

var setter = new Function("obj", "newval", "obj." + path + " = newval;");
setter(obj, "some new val");

1
同じパスを長期間離れて逆参照する必要がある場合は、上のjsperf.comリンクに、関数を保存して後で検索する方法の例が示されています。Functionコンストラクターを呼び出す動作はかなり遅いので、高パフォーマンスのコードは結果をメモして、可能であればそれを繰り返さないようにする必要があります。
Kevin Crumley、


7

パスを分割して反復し、オブジェクトを減らすことをお勧めします。この提案は、欠落しているプロパティのデフォルト値で機能します。

const getValue = (object, keys) => keys.split('.').reduce((o, k) => (o || {})[k], object);

console.log(getValue({ a: { b: '1', c: '2' } }, 'a.b'));
console.log(getValue({ a: { b: '1', c: '2' } }, 'foo.bar.baz'));


6

Lodashをすでに使用している場合は、propertyまたはget関数を使用できます。

var obj = { a: { b: '1', c: '2' } };
_.property('a.b')(obj); // => 1
_.get(obj, 'a.b'); // => 1

アンダースコアにもproperty機能がありますが、ドット表記はサポートされていません。


1
アンダースコアはドット表記をサポートしていないようです。回答を更新しました。
タムリン2016年

5

他の提案は少し不可解なので、私は貢献すると思いました:

Object.prop = function(obj, prop, val){
    var props = prop.split('.')
      , final = props.pop(), p 
    while(p = props.shift()){
        if (typeof obj[p] === 'undefined')
            return undefined;
        obj = obj[p]
    }
    return val ? (obj[final] = val) : obj[final]
}

var obj = { a: { b: '1', c: '2' } }

// get
console.log(Object.prop(obj, 'a.c')) // -> 2
// set
Object.prop(obj, 'a.c', function(){})
console.log(obj) // -> { a: { b: '1', c: [Function] } }

5

このプロセスを簡素化するnpmで利用可能なライブラリを使用できます。https://www.npmjs.com/package/dot-object

 var dot = require('dot-object');

var obj = {
 some: {
   nested: {
     value: 'Hi there!'
   }
 }
};

var val = dot.pick('some.nested.value', obj);
console.log(val);

// Result: Hi there!

1
これをお知らせいただきありがとうございます。とても便利に見えます。
nevf

そしてこれが、すべてのプロジェクトが10000の依存関係を注入する理由です:-)。
Marvin

4
var a = { b: { c: 9 } };

function value(layer, path, value) {
    var i = 0,
        path = path.split('.');

    for (; i < path.length; i++)
        if (value != null && i + 1 === path.length)
            layer[path[i]] = value;
        layer = layer[path[i]];

    return layer;
};

value(a, 'b.c'); // 9

value(a, 'b.c', 4);

value(a, 'b.c'); // 4

これは、はるかに単純なeval方法と比較すると多くのコードですが、Simon Willisonが言うように、evalを使用しないください

また、JSFiddle


すばらしい、これはごちそうであり、CD Sanchezよりも短いです。ありがとう。
nevf

value(a、 'bbbbb')はundefinedを返しません
frumbert

4

ninjageckoによるエレガントな回答を拡張して、関数がドット付きおよび/または配列スタイルの参照の両方を処理し、空の文字列が親オブジェクトを返すようにしました。

どうぞ:

string_to_ref = function (object, reference) {
    function arr_deref(o, ref, i) { return !ref ? o : (o[ref.slice(0, i ? -1 : ref.length)]) }
    function dot_deref(o, ref) { return ref.split('[').reduce(arr_deref, o); }
    return !reference ? object : reference.split('.').reduce(dot_deref, object);
};

ここで私のjsFiddleの例を参照してください:http ://jsfiddle.net/sc0ttyd/q7zyd/


本当に良い解決策です。問題は1つだけ[]です。表記法は常に配列を想定しています。オブジェクトキーは、同様の例は、のためにそのように表現があることができobj['some-problem/name'].list[1]、この私が更新しなければならなかった修正するためにarr_deref、このような機能 javascript function arr_deref(o, ref, i) { return !ref ? o : (o[(ref.slice(0, i ? -1 : ref.length)).replace(/^['"]|['"]$/g, '')]); }
セルカンYersen

1
でも最近はそうしないけど。Lodashを使用します:lodash.com/docs#get
Sc0ttyD '10

2
var find = function(root, path) {
  var segments = path.split('.'),
      cursor = root,
      target;

  for (var i = 0; i < segments.length; ++i) {
   target = cursor[segments[i]];
   if (typeof target == "undefined") return void 0;
   cursor = target;
  }

  return cursor;
};

var obj = { a: { b: '1', c: '2' } }
find(obj, "a.b"); // 1

var set = function (root, path, value) {
   var segments = path.split('.'),
       cursor = root,
       target;

   for (var i = 0; i < segments.length - 1; ++i) {
      cursor = cursor[segments[i]] || { };
   }

   cursor[segments[segments.length - 1]] = value;
};

set(obj, "a.k", function () { console.log("hello world"); });

find(obj, "a.k")(); // hello world

迅速な対応ありがとうございました。eval()ソリューションは好きではありません。これと同様の投稿が最もよく見えます。しかし、私はまだ問題を抱えています。値obj.ab = new valueを設定しようとしています。正確にはbの値は関数なので、obj.ab(new_value)を使用する必要があります。関数が呼び出されましたが、値が設定されていません。スコープの問題だと思いますが、まだ調査中です。これは元の質問の範囲外であると思います。私のコードはKnockout.jsを使用しており、bはko.observableです。
nevf

@nevf:私はあなたが望むことをすると思う2つ目の関数を追加しました。必要な動作に応じて、必要に応じてカスタマイズできます(たとえば、オブジェクトが存在しない場合にオブジェクトを作成する必要があるかどうかなど)。
クリスティアンサンチェス

@nevfしかし、私は1つの関数でそれを行います。; D
マッケイラ

更新していただきありがとうございます。@tylermwashburn-そして、御馳走としても機能する短い実装に感謝します。すべてに素晴らしいw / eを持っています。
nevf

2

オブジェクトメンバーの値は、1行のコードを使用したドット表記で取得できます。

new Function('_', 'return _.' + path)(obj);

あなたの場合:

var obj = { a: { b: '1', c: '2' } }
var val = new Function('_', 'return _.a.b')(obj);

簡単にするために、次のような関数を書くことができます:

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

説明:

Functionコンストラクタは、新しいFunctionオブジェクトを作成します。JavaScriptでは、すべての関数は実際にはFunctionオブジェクトです。関数コンストラクターで関数を明示的に作成する構文は次のとおりです。

new Function ([arg1[, arg2[, ...argN]],] functionBody)

ここでarguments(arg1 to argN)、は有効なjavaScript識別子に対応する文字列である必要がありfunctionBody、関数定義を構成するjavaScriptステートメントを含む文字列です。

この例では、文字列関数の本体を利用して、ドット表記でオブジェクトメンバーを取得します。

それが役に立てば幸い。


これが何をしているか正確に説明できますか?そして、「ab」などを関数パラメータとしてどのように渡しますか?
nevf 2015

1
私はこれに+1を与えましたが、JSLintは「関数コンストラクタはevalの形式である」と警告しています。
gabe

2

ネイティブ反応するGET / SET回答(Object.prototype現在は割り当てられません):

Object.defineProperty(Object.prototype, 'getNestedProp', {
    value: function(desc) {
        var obj = this;
        var arr = desc.split(".");
        while(arr.length && (obj = obj[arr.shift()]));
        return obj;
    },
    enumerable: false
});

Object.defineProperty(Object.prototype, 'setNestedProp', {
    value: function(desc, value) {
        var obj = this;
        var arr = desc.split(".");
        var last = arr.pop();
        while(arr.length && (obj = obj[arr.shift()]));
        obj[last] = value;
    },
    enumerable: false
});

使用法:

var a = { values: [{ value: null }] };
var b = { one: { two: 'foo' } };

a.setNestedProp('values.0.value', b.getNestedProp('one.two'));
console.log(a.values[0].value); // foo

1

Ricardo Tomasiの回答から以下をコピーし、必要に応じてまだ存在しないサブオブジェクトも作成するように変更しました。それは少し効率的ではありませんが(より多くifのsと空のオブジェクトの作成)、かなり良いはずです。

また、Object.prop(obj, 'a.b', false)以前はできなかった場所でもできるようになります。残念ながら、それでも割り当てを行うことはできundefinedません...その方法についてはまだわかりません。

/**
 * Object.prop()
 *
 * Allows dot-notation access to object properties for both getting and setting.
 *
 * @param {Object} obj    The object we're getting from or setting
 * @param {string} prop   The dot-notated string defining the property location
 * @param {mixed}  val    For setting only; the value to set
 */
 Object.prop = function(obj, prop, val){
   var props = prop.split('.'),
       final = props.pop(),
       p;

   for (var i = 0; i < props.length; i++) {
     p = props[i];
     if (typeof obj[p] === 'undefined') {
       // If we're setting
       if (typeof val !== 'undefined') {
         // If we're not at the end of the props, keep adding new empty objects
         if (i != props.length)
           obj[p] = {};
       }
       else
         return undefined;
     }
     obj = obj[p]
   }
   return typeof val !== "undefined" ? (obj[final] = val) : obj[final]
 }

1

ドット表記キーを含むオブジェクトをそれらのキーの配列バージョンに変換する場合は、これを使用できます。


これは次のようなものに変換されます

{
  name: 'Andy',
  brothers.0: 'Bob'
  brothers.1: 'Steve'
  brothers.2: 'Jack'
  sisters.0: 'Sally'
}

{
  name: 'Andy',
  brothers: ['Bob', 'Steve', 'Jack']
  sisters: ['Sally']
}

convertDotNotationToArray(objectWithDotNotation) {

    Object.entries(objectWithDotNotation).forEach(([key, val]) => {

      // Is the key of dot notation 
      if (key.includes('.')) {
        const [name, index] = key.split('.');

        // If you have not created an array version, create one 
        if (!objectWithDotNotation[name]) {
          objectWithDotNotation[name] = new Array();
        }

        // Save the value in the newly created array at the specific index 
        objectWithDotNotation[name][index] = val;
        // Delete the current dot notation key val
        delete objectWithDotNotation[key];
      }
    });

}

JSONのように「brothers.0.one」で値を処理しませんでした。
Karthick

配列コレクションでより複雑なJSONを処理するための提案はありますか?
Karthick、

0

これはを使用しないコードevalです。分かりやすいです。

function value(obj, props) {
  if (!props) return obj;
  var propsArr = props.split('.');
  var prop = propsArr.splice(0, 1);
  return value(obj[prop], propsArr.join('.'));
}

var obj = { a: { b: '1', c: '2', d:{a:{b:'blah'}}}};

console.log(value(obj, 'a.d.a.b')); //returns blah

0

はい、それは4年前に尋ねられました。はい、通常、基本プロトタイプを拡張することは良い考えではありませんが、すべての拡張機能を1か所にまとめておくと便利です。
だから、これが私のやり方です。

   Object.defineProperty(Object.prototype, "getNestedProperty", {
    value     : function (propertyName) {
        var result = this;
        var arr = propertyName.split(".");

        while (arr.length && result) {
            result = result[arr.shift()];
        }

        return result;
    },
    enumerable: false
});

これで、関数またはコピー/貼り付け関数を含むモジュールをインポートしなくても、どこでもネストされたプロパティを取得できます。

UPD。例:

{a:{b:11}}.getNestedProperty('a.b'); //returns 11

UPD 2.次の拡張機能は、プロジェクトでマングースを壊します。また、jqueryが壊れる可能性があることも読みました。だから、次の方法でそれをしないでください

 Object.prototype.getNestedProperty = function (propertyName) {
    var result = this;
    var arr = propertyName.split(".");

    while (arr.length && result) {
        result = result[arr.shift()];
    }

    return result;
};

0

これが私の実装です

実装1

Object.prototype.access = function() {
    var ele = this[arguments[0]];
    if(arguments.length === 1) return ele;
    return ele.access.apply(ele, [].slice.call(arguments, 1));
}

実装2(スライスの代わりに配列削減を使用)

Object.prototype.access = function() {
    var self = this;
    return [].reduce.call(arguments,function(prev,cur) {
        return prev[cur];
    }, self);
}

例:

var myobj = {'a':{'b':{'c':{'d':'abcd','e':[11,22,33]}}}};

myobj.access('a','b','c'); // returns: {'d':'abcd', e:[0,1,2,3]}
myobj.a.b.access('c','d'); // returns: 'abcd'
myobj.access('a','b','c','e',0); // returns: 11

配列内のオブジェクトも処理できます

var myobj2 = {'a': {'b':[{'c':'ab0c'},{'d':'ab1d'}]}}
myobj2.access('a','b','1','d'); // returns: 'ab1d'

0

これは私の提案する拡張ソリューションです。ninjagecko

私にとっては、単純な文字列表記では不十分だったため、以下のバージョンでは次のようなものがサポートされています。

index(obj、 'data.accounts [0] .address [0] .postcode');

/**
 * Get object by index
 * @supported
 * - arrays supported
 * - array indexes supported
 * @not-supported
 * - multiple arrays
 * @issues:
 *  index(myAccount, 'accounts[0].address[0].id') - works fine
 *  index(myAccount, 'accounts[].address[0].id') - doesnt work
 * @Example:
 * index(obj, 'data.accounts[].id') => returns array of id's
 * index(obj, 'data.accounts[0].id') => returns id of 0 element from array
 * index(obj, 'data.accounts[0].addresses.list[0].id') => error
 * @param obj
 * @param path
 * @returns {any}
 */
var index = function(obj, path, isArray?, arrIndex?){

    // is an array
    if(typeof isArray === 'undefined') isArray = false;
    // array index,
    // if null, will take all indexes
    if(typeof arrIndex === 'undefined') arrIndex = null;

    var _arrIndex = null;

    var reduceArrayTag = function(i, subArrIndex){
        return i.replace(/(\[)([\d]{0,})(\])/, (i) => {
            var tmp = i.match(/(\[)([\d]{0,})(\])/);
            isArray = true;
            if(subArrIndex){
                _arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }else{
                arrIndex =  (tmp[2] !== '') ? tmp[2] : null;
            }
            return '';
        });
    }

    function byIndex(obj, i) {
        // if is an array
        if(isArray){
            isArray = false;
            i = reduceArrayTag(i, true);
            // if array index is null,
            // return an array of with values from every index
            if(!arrIndex){
                var arrValues = [];
                _.forEach(obj, (el) => {
                    arrValues.push(index(el, i, isArray, arrIndex));
                })
                return arrValues;
            }
            // if array index is specified
            var value = obj[arrIndex][i];
            if(isArray){
                arrIndex = _arrIndex;
            }else{
                arrIndex = null;
            }
            return value;
        }else{
            // remove [] from notation,
            // if [] has been removed, check the index of array
            i = reduceArrayTag(i, false);
            return obj[i]
        }
    }

    // reduce with byIndex method
    return path.split('.').reduce(byIndex, obj)
}

0

死んだ馬を倒す危険があります...これは、ネストされたオブジェクトをトラバースして、ベースオブジェクトまたは同じ構造を持つ類似のオブジェクトに関してどこにいるかを参照するのに最も役立ちます。そのために、これはネストされたオブジェクトトラバーサル関数で役立ちます。パスを保持するために配列を使用したことに注意してください。文字列パスまたは配列を使用するようにこれを変更するのは簡単です。また、他の一部の実装とは異なり、値に「未定義」を割り当てることができることにも注意してください。

/*
 * Traverse each key in a nested object and call fn(curObject, key, value, baseObject, path)
 * on each. The path is an array of the keys required to get to curObject from
 * baseObject using objectPath(). If the call to fn() returns falsey, objects below
 * curObject are not traversed. Should be called as objectTaverse(baseObject, fn).
 * The third and fourth arguments are only used by recursion.
 */
function objectTraverse (o, fn, base, path) {
    path = path || [];
    base = base || o;
    Object.keys(o).forEach(function (key) {
        if (fn(o, key, o[key], base, path) && jQuery.isPlainObject(o[key])) {
            path.push(key);
            objectTraverse(o[key], fn, base, path);
            path.pop();
        }
    });
}

/*
 * Get/set a nested key in an object. Path is an array of the keys to reference each level
 * of nesting. If value is provided, the nested key is set.
 * The value of the nested key is returned.
 */
function objectPath (o, path, value) {
    var last = path.pop();

    while (path.length && o) {
        o = o[path.shift()];
    }
    if (arguments.length < 3) {
        return (o? o[last] : o);
    }
    return (o[last] = value);
}


0

私のプロジェクトでこのコードを使用しました

const getValue = (obj, arrPath) => (
  arrPath.reduce((x, y) => {
    if (y in x) return x[y]
    return {}
  }, obj)
)

使用法:

const obj = { id: { user: { local: 104 } } }
const path = [ 'id', 'user', 'local' ]
getValue(obj, path) // return 104

0

数年後、私はスコープと配列を処理するこれを見つけました。例えばa['b']["c"].d.etc

function getScopedObj(scope, str) {
  let obj=scope, arr;

  try {
    arr = str.split(/[\[\]\.]/) // split by [,],.
      .filter(el => el)             // filter out empty one
      .map(el => el.replace(/^['"]+|['"]+$/g, '')); // remove string quotation
    arr.forEach(el => obj = obj[el])
  } catch(e) {
    obj = undefined;
  }

  return obj;
}

window.a = {b: {c: {d: {etc: 'success'}}}}

getScopedObj(window, `a.b.c.d.etc`)             // success
getScopedObj(window, `a['b']["c"].d.etc`)       // success
getScopedObj(window, `a['INVALID']["c"].d.etc`) // undefined

-1

あなたの質問が何であるか明確ではありません。オブジェクトが与えられた場合、obj.a.bそれはそのまま「2」になります。大括弧を使用するようにストリングを操作したい場合は、次のようにすることができます。

var s = 'a.b';
s = 'obj["' + s.replace(/\./g, '"]["') + '"]';
alert(s); // displays obj["a"]["b"]

1
これはに対しては機能せずa.b.c、彼らが望んでいることを実際に達成するものでもありません。彼らはevalパスではなく値を求めています。
McKayla

で動作するように修正しましたa.b.cが、あなたは正しいです。どうやら彼はでプロパティの値を取得/設定したいと考えていましたobj.a.b。彼が「文字列を変換する」ことを望んでいると彼が言ったので、質問は私を混乱させました...
Mark Eirich

よくやった。:)それは少しあいまいでした。あなたは変換の良い仕事をしました。
マッケイラ

-1

ここに私の10セントがあります、怒鳴る機能は与えられたパスに基づいて取得/設定します、あなたがそれを改善できることを確認してください|| とに置き換えるObject.hasOwnProperty、あなたが誤って偽の値を気にしない場合

私はそれをa.b.cab2.cでテスト{a:{b:[0,1,{c:7}]}}し、設定と取得の両方で動作しました:)。

Cheerz

function helper(obj, path, setValue){
  const l = String(path).split('.');
  return l.reduce((o,i, idx)=>{
   if( l.length-idx===1)  { o[i] = setValue || o[i];return setValue ? obj : o[i];}
  o[i] = o[i] || {};
   return o[i];
  }, x)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.