[解決済み】ドット記法のJavaScript文字列をオブジェクト参照に変換する
質問
JavaScriptのオブジェクトを指定します。
var obj = { a: { b: '1', c: '2' } }
と文字列
"a.b"
文字列をドット表記に変換して、次のように入力します。
var val = obj.a.b
もし、この文字列がただの
'a'
を使用することができます。
obj[a]
. しかし、これはより複雑です。何か簡単な方法があるのでしょうが、今のところ私にはわかりません。
解決方法は?
<ブロッククオート最近のメモ この回答が多くのupvoteを得たことは喜ばしいことですが、私は少々恐ろしくもあります。もし、"x.a.b.c"のようなドット注記の文字列を参照に変換する必要があるなら、それは(多分)何か非常に間違ったことが起こっている兆候かもしれません(多分、何かおかしなデシリアライズを行っているのでなければですが)。
<ブロッククオートつまり、この答えにたどり着いた初心者は、「"なぜ私はこんなことをしているのだろう?
<ブロッククオートもちろん、ユースケースが小さく、パフォーマンスの問題が発生しないのであれば、この方法で問題ないでしょう。実際、もしこれでコードの複雑さが軽減され、物事がシンプルになるのなら、あなたは すべき おそらく、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"
.)
を使うのがまだ怖いという人に向けて。
reduce
ECMA-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(...)
. 最後に余談ですが、元の質問者はインデックスの配列を扱うことができる(はず?
.split
しかし、便利な関数であることに間違いはありません。
コメント欄からの質問です。
配列の場合はどうなんでしょうか?-アレックス
Javascriptは非常に奇妙な言語で、一般的にオブジェクトはプロパティのキーとして文字列しか持つことができません。
x
のような一般的なオブジェクトでした。
x={}
であれば
x[1]
になります。
x["1"]
... 読んだ通りだ...そうだ...。
Javascriptの配列(それ自体がObjectのインスタンス)は、特に整数のキーを推奨しており、次のようなことが可能です。
x=[]; x["puppy"]=5;
.
しかし、一般的には(例外もありますが)。
x["somestring"]===x.somestring
(許される場合。
x.123
).
(使用しているJSコンパイラが、仕様に違反しないことを証明できれば、これらをより健全な表現にコンパイルすることを選択するかもしれないことを心に留めておいてください)。
つまり、質問の答えは、これらのオブジェクトが(問題領域の制限により)整数しか受け入れないと仮定しているか、そうでないかによって変わってくるということです。そうでないと仮定しましょう。その場合、有効な式は基本識別子といくつかの
.identifier
に加えて、いくつかの
["stringindex"]
s.
もちろん、文法上では他にも以下のようなことが正当に行えるということは、ちょっと無視しましょう。
identifier[0xFA7C25DD].asdf[f(4)?.[5]+k][false][null][undefined][NaN]
整数は(それほど)「特別」ではないのです。
この場合、コメンテーターの発言は次のようになります。
a["b"][4]["c"]["d"][1][2][3]
をサポートする必要があります。
a.b["c\"validjsstringliteral"][3]
. を確認する必要がありますね。
文字列リテラルに関する ecmascript の文法セクション
をクリックすると、有効な文字列リテラルをパースする方法を見ることができます。技術的には、(私の最初の回答とは異なり)あなたはまた、以下のことを確認したいと思うでしょう。
a
は有効な
javascriptの識別子
.
簡単な答えですが。
文字列にカンマや括弧が含まれていない場合
にない長さ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年特別編集。
このまま一周して、思いつく限り最も非効率的で、恐ろしくオーバーメタな解決策を実行しましょう...構文的な観点で <ストライク 純度 のハムフィステルです。ES6のProxyオブジェクトで!... また、不適切に書かれたライブラリを破壊する可能性のあるプロパティも定義しておきましょう。もし、あなたがパフォーマンスや正気(あなたや他の人の)、仕事などを気にしているのなら、これを使うのは注意すべきかもしれません。
// [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['a.b.c'] is: 1
<ブロッククオート(プロキシ オーバーライド セット) objHyper['a.b.c']=3, now obj is: {"a":{"b":{"c":3,"d":2}}} となりました。
<ブロッククオート(裏)objHyperは。プロキシ {a: {...}}
<ブロッククオート(ショートカット) obj.H['a.b.c']=4
<ブロッククオート(ショートカット) obj.H['a.b.c'] is obj['a']['b']['c'] is: 4
非効率的なアイデア: 上記を修正して、入力引数に基づいてディスパッチすることができます。
.match(/[^\]\[.]+/g)
メソッドでサポートします。
obj['keys'].like[3]['this']
または、もし
instanceof Array
のように配列を入力として受け取ります。
keys = ['a','b','c']; obj.H[keys]
.
未定義のインデックスを「よりソフトな」NaNスタイルで処理したいのではないかという提案につき、(例えば
index({a:{b:{c:...}}}, 'a.x.c')
はキャッチされないTypeErrorではなく、undefinedを返す)...:
-
これは、1次元のインデックスの状況({})['e.g.']==undefinedでは、"エラーを投げるよりも未定義を返すべき"という観点から、N次元の状況でも意味をなします。
-
これは ない という観点では意味がありません。
x['a']['x']['c']
この場合、上記の例ではTypeErrorで失敗します。
とはいえ、還元関数をどちらかに置き換えることで、うまくいくでしょう。
(o,i)=> o===undefined?undefined:o[i]
または
(o,i)=> (o||{})[i]
.
(これをより効率的にするには、for ループを使用して、次にインデックスを作成するサブ結果が未定義になるたびに中断/リターンするか、そのような失敗が十分にまれであると期待するならば try-catch を使用します)。
関連
-
[解決済み】JavaScriptのisset()に相当するもの
-
[解決済み] 解決済み】clearInterval()が動作しない [重複] [重複]
-
[解決済み] JavaScriptで文字列が部分文字列を含むかどうかを確認する方法は?
-
[解決済み] JavaScriptでオブジェクトをディープクローンする最も効率的な方法は何ですか?
-
[解決済み] JavaScriptのオブジェクトが空であることをテストするにはどうすればよいですか?
-
[解決済み] JavaScriptのオブジェクトにキーが存在するかどうかをチェックする?
-
[解決済み] JavaScriptで文字列をbooleanに変換するにはどうしたらいいですか?
-
[解決済み] JavaScriptで日付の書式設定に関するドキュメントはどこにありますか?
-
[解決済み】JavaScriptで文字列の出現箇所をすべて置換する方法
-
[解決済み】オブジェクトからプロパティを削除する(JavaScript)
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】TypeError: $(...).DataTable は関数ではありません。
-
[解決済み】「Uncaught TypeError: Chromeで "Illegal invocation "が発生する。
-
[解決済み】JavaScript ランタイムエラー:'$'が未定義です。
-
[解決済み】エラー:リスン EACCES 0.0.0.0:80 OSx Node.js
-
[解決済み】 Uncaught Error: Invariant Violation: 解決済み】 Uncaught Error: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function but got: object.
-
[解決済み】Babel NodeJS ES6: SyntaxError: 予期しないトークンのエクスポート
-
[解決済み】 Uncaught Reference Error: stLight is not defined (in Chrome only)
-
[解決済み】Uncaught TypeError: 未定義のプロパティ 'msie' を読み取れない - jQuery tools
-
[解決済み】 \u003C とは何ですか?
-
[解決済み】未定義のプロパティ 'forEach' を読み取ることができない