javascriptでkey-valueペアの配列からオブジェクトを生成する方法を1つ発見(?)したので共有したいと思います。
結論
arr = [[key1, value1], [key2, value2], ...]
Object.assign(...arr.map(([k,v]) => ({[k]:v})))
// => {key1: value1, key2: value2, ...}
解説
最近のjsにはスプレッド構文という便利なものがあって、iterableから簡単にオブジェクトを生成できます。
obj = {a:1, b:2}
clone = {...obj} // clone = {a:1, b:2}
obj===clone // => false
// 複数のオブジェクトもok
obj2 = {c: 3}
merged = {...obj, ...obj2} // merged = {a:1, b:2, c:3}
// Object.assignの代わりとして
Object.assign({}, obj, obj2) // => {a:1, b:2, c:3}
MDN docsにもあるように、この構文はObject.assign
の機能を一部代替できますが、複数のオブジェクトとして、配列にたくさん入っているようなものを順次展開するというようなことはできません。
objs = [{a:1, b:2}, {c:3}]
expand = {...objs}
// objsはオブジェクトというより配列なので、もちろんうまくいかない
// expand = {0: {a: 1, b: 2}, 1: {c: 3}}
// expand = {...[...{a:1, b:2}, ...{c:3}]}のようにかけたらうまくいきそうだけど...
ここのobjsはオブジェクトというより配列なので、objs = {0: {a: 1, b: 2}, 1: {c: 3}}
というオブジェクトのように振舞います。
ここで、改めてObject.assign
を見ると、これは複数の複数のオブジェクトを引数に受け取って、先頭のオブジェクトに中身を追加していきます。
obj1 = {a:1, b:2}
obj2 = {c: 3}
Object.assign({}, obj1, obj2) // => {a:1, b:2, c:3}
// {...obj1, ...obj2}
// Object.assign(obj1, obj2)とかくと、obj1の中身が変わってしまうので注意
{...obj1, ...obj2}
の方が楽なので、Object.assign
はあまり使い道がないのかと思いましたが、「引数展開のスプレッド構文」を使うと、オブジェクト生成のスプレッド構文にはできないことができることに気づきました。すなわち
objs = [{a:1, b:2}, {c:3}]
Object.assign({}, objs[0], objs[1]) // => {a:1, b:2, c:3}
// この代わりに
Object.assign({}, ...objs) // => {a:1, b:2, c:3}
のようなことができるので、[[key1, value1], [key2, value2], ...]
というようなkey-valueペアの配列があった時、まず[{key1: value1}, {key2: value2},...]
のようなkeyが1つオブジェクトたちの配列に変換して、Object.assign
に突っ込めばうまくいきそうです。一つ目の変換はmap
でできます。
arr = [['a', 1], ['b', 2], ['c', 3]]
objs = arr.map(([k,v]) => ({[k]: v}))
2行目でやけに括弧が多いような気もしますが、注意点としては
1 ) アロー関数の引数([k,v])
について。
これは引数として配列を受け取り、配列の第0要素をk, 第1要素をvにしますという意味です。単にmap([k,v] => ...)
と書けそうな気がしましたが、([k,v])
のように外側の丸括弧も必要みたいです。
2 ) アロー関数の値({key: value})
について。
アロー関数でオブジェクトリテラルを返すときは、外側を丸括弧で囲う必要があります。これはアロー関数の特別な文法というわけではなく、オブジェクトを生成してすぐに使うような場合(実例あるのかよくわかりませんが)でも使えます。
{a:1}.a // => SyntaxError: Unexpected token '.'
({a:1}).a // => 1
({a:1, b:2})['b'] // => 2
ただの{}
だとブロックの中括弧と紛らわしいところを、外側を丸括弧で囲うとしっかりとしたオブジェクトになるようなイメージで良いかと。
3 ) オブジェクトリテラルの({[k]:v})
の[k]
について。
単に({k:v})
とやると例えばk='a', v=1
の時は、{a:1}
ではなく、{k:1}
になってしまいます。kが変数ではなくそのままリテラルとして解釈されてしまうためです。これを防ぐには[k]
として変数の中身を展開する必要があります。
オブジェクトリテラルの書き方は通常の{k: v}
以外に色々あります。
オブジェクトリテラルのプロパティ/メソッドのいろんな書き方(ES6版)
が参考になりました。
というわけで、オブジェクトたちの配列に変換できたので、あとはObject.assign
に突っ込めばオッケーです。
Object.assign(...objs) // => {a: 1, b: 2, c: 3}
通常新しいオブジェクトを作るときは、既存のオブジェクトの中身を変えないためにObject.assign
の第一引数は空のオブジェクト{}
にすると思いますが、この場合は直前にarr.map
で作ったオブジェクトがあるので、そこに追加するという形で問題ないでしょう。
arr = [['a', 1], ['b', 2], ['c', 3]]
objs = arr.map(([k,v]) => ({[k]: v})) // => [{a: 1}, {b: 2}, {c: 3}]
Object.assign(...objs)
// Object.assign({a: 1}, {b: 2}, {c: 3})と同じで、無名の{a: 1}に追加するという形になる。
// 配列の長さが1(引数が一つ)の場合でもok
Object.assign(...[{a:1}]) // => {a:1}
// 長さゼロだと残念ながらエラー
Object.assign(...[]) // => TypeError: Object.assign requires that input parameter not be null or undefined
// key-valueペア配列の長さがゼロの時に空のオブジェクトを返してくれるようにするには、第一引数に{}を入れとくのが無難かもしれない
objs = []
Object.assign({}, ...objs) // => {}
まとめ
スプレッド構文は便利!