はじめに
記事を見ていただいて、ありがとうございます!
Webエンジニアをしているsannoと申します。
JavaScriptでObjectやArrayを操作することは、よくありますよねー
何かFrontendのアプリケーションから、APIを呼び出したときにこっちはArrayで返ってくるけれどこっちはObjectで返ってきて、でもどちらもArrayとして共通で処理できたらうれしいなみたいな。
ObjectやArrayを共通で処理したいと思った時に、何かのヒントになるかもしれませんので良かったら見ていってください。
なお、この記事でのObjectは連想配列のことを指して解説しています。
ObjectとArrayの解説については以下の記事を参照していただくと良いかもしれません。
もし、この記事で解説している内容に誤りがございましたら、コメント等でご指摘いただけると幸いです。
それでは、多次元ObjectとArrayを一次元Arrayに変換する方法を見てゆきましょう!
ObjectとArrayを一次元Arrayに変換するfunction
これが変換functionです!
const convertAnyIntoFlatArray = (anyValue) => {
if (anyValue === null) {
return [];
}
if (typeof(anyValue) !== 'object') {
return [anyValue];
}
return convertRecursively(anyValue);
};
const convertRecursively = (anyValue) => {
if (Array.isArray(anyValue)) {
return anyValue.reduce((previousValue, currentValue) => {
return typeof(currentValue) === 'object' ? previousValue.concat(convertRecursively(currentValue)) : previousValue.concat(currentValue);
}, []);
}
return convertRecursively(
Object.keys(anyValue).map((key) => {return anyValue[key];})
);
}
const value1 = [1, 2, [3, 4, [5, 6, [7, 8]]]]
console.log(convertAnyIntoFlatArray(value1))
// > [1, 2, 3, 4, 5, 6, 7, 8]
const value2 = {key1: 1, key2: 2, key3: {key4: 4, key5: 5}}
console.log(convertAnyIntoFlatArray(value2))
// > [1, 2, 4, 5]
const value3 = ['item1', 'item2', ['item3', ['item4', {key1: 'item5', key2: 'item6'}]], {key3: 'item7', key4: 'item8', key5: ['item9', 'item10'], key6: {key7: 'item11', key8: 'item12'}}]
console.log(convertAnyIntoFlatArray(value3))
// > ['item1', 'item2", 'item3', 'item4', 'item5', 'item6', 'item7', 'item8', 'item9', 'item10', 'item11', 'item12']
const value4 = 1
console.log(convertAnyIntoFlatArray(value4))
// > [1]
const value5 = 'item1'
console.log(convertAnyIntoFlatArray(value5))
// > ['item1']
const value6 = null
console.log(convertAnyIntoFlatArray(value6))
// > []
const value7 = []
console.log(convertAnyIntoFlatArray(value7))
// > []
const value8 = {}
console.log(convertAnyIntoFlatArray(value8))
// > []
functionはこのような感じです。
上から順にポイントになる部分を解説します。
ポイント1. typeofのobject
以下の条件判定ですね。
if (typeof(anyValue) !== 'object') {}
typeofは型を調べる演算子です。
typeofで’object’が返ってくるのはObjectとArrayも含まれます。
ポイント2. reduce()
reduce()は以下の処理で使われていますね。
if (Array.isArray(anyValue)) {
return anyValue.reduce((previousValue, currentValue) => {
return typeof(currentValue) === 'object' ? previousValue.concat(convertRecursively(currentValue)) : previousValue.concat(currentValue);
}, []);
}
reduce()はArrayの組み込みの関数です。
reduce()はArrayの各要素に対して、繰り返し処理を行います。
繰り返し処理の結果を次の繰り返し処理に使用して、結果を得る関数といったイメージです
ポイント3. 再帰関数
convertRecursively()は再起関数です。
const convertRecursively = (anyValue) => {
if (Array.isArray(anyValue)) {
return anyValue.reduce((previousValue, currentValue) => {
return typeof(currentValue) === 'object' ? previousValue.concat(convertRecursively(currentValue)) : previousValue.concat(currentValue);
}, []);
}
return convertRecursively(
Object.keys(anyValue).map((key) => {return anyValue[key];})
);
}
再起とはWikipediaによると以下のような意味です。
再帰(さいき)は、あるものについて記述する際に、記述しているものそれ自身への参照が、その記述中にあらわれることをいう。
https://ja.wikipedia.org/wiki/%E5%86%8D%E5%B8%B0
ですので、再帰関数は自身の中で再度、自身を呼び出しているような関数といった感じですかね。
再起関数ってわかりづらいですよね。
再帰関数の考え方のポイントは、どこで再起処理がbreakして処理が巻き戻るのかをとらえることかなと思っています。
今回の再帰関数だと、以下のpreviousValue.concat(currentValue)がそれにあたります。
ObjectやArrayが再起関数にわたされると、何度も再帰関数がよびだされますが、それ以外の値がわたされると再起処理がbreakして処理が巻き戻るというようなイメージです。
return typeof(currentValue) === 'object' ? previousValue.concat(convertRecursively(currentValue)) : previousValue.concat(currentValue);
ポイント4. ObjectをArrayに変換する
ObjectをArrayに変換している処理は以下です。
Object.keys(anyValue).map((key) => {return anyValue[key];})
Objectはmap()を直接は実行できないので、Object.keys()でkeyのArrayを取り出して、それに対してmap()を実行することでArrayに変換するというイメージです。
おわりに
多次元ObjectとArrayを一次元Arrayに変換する方法はいかがでしたでしょうか?
どんな引数でも受け付けるfunctionってどうなのとか、計算量やばくねみたいなつっこみはあるかもしれませんね…
計算量については、以下のArray.prototype.flat()とか使って改造してもらった方が軽い処理になるかもしれません。
また、ちょっとObjectのあたりの処理がやりたいことと違うんだよなーという方もいるかもしれません。
そういった方は、Object.entries()なんかを参照していただくと、もしかしたらやりたいことの方法がみつかるかもしれません。
この記事がなにかのヒントになりましたら、幸いです。
最後まで記事を読んでいただいて、ありがとうございました!!
コメント