ドットで区切ったキーからキー配列をつくりネストしたオブジェクトを生成してマージ

環境

  • JavaScript (es5)

今回したいこと

  • ドットで区切ったキーがあって、値がある
  • ドットで区切ったキーはドットの部分でネストしたオブジェクトにしたい
  • キー、値が複数ありネストしたオブジェクトのマージもしたい

元の値

キー
keyA-0.keyA-1.keyA-2 valAAA
keyA-0.keyA-1.keyB-2 valAAB
keyB-0.keyB-1.keyB-2 valBBB
keyB-0.keyC-1.keyC-2 valBCC

期待値

{
    keyA-0: {
        keyA-1: {
            keyA-2:"valAAA",
            keyB-2:"valAAB"
        }
    },
    keyB-0: {
        keyB-1: {
            keyB-2:"valBBB"
        },
        keyC-1: {
            keyC-2:"valBCC"
        }
    }
}

ネストしたオブジェクトを生成してマージをする関数

const AAAKeyStr = 'keyA-0.keyA-1.keyA-2';
const AAAKey    = AAAKeyStr.split('.');  // ドット区切って配列にする
const AAAVal    = 'valAAA';

const AABKeyStr = 'keyA-0.keyA-1.keyB-2'
const AABKey    = AABKeyStr.split('.'); // ドット区切って配列にする
const AABVal    = 'valAAB'

const BBBKeyStr = 'keyB-0.keyB-1.keyB-2'
const BBBKey    = BBBKeyStr.split('.'); // ドット区切って配列にする
const BBBVal    = 'valBBB'

const BCCKeyStr = 'keyB-0.keyC-1.keyC-2'
const BCCKey    = BCCKeyStr.split('.'); // ドット区切って配列にする
const BCCVal    = 'valBCC';

/**
 * キー配列からネストしたオブジェクトを作成しながらマージ
 */
const createNestObj = (obj, keys, val, idx = 0) => { 
    if (keys.length === idx) return val;
    if (!obj[keys[idx]]) {
      obj[keys[idx]] = {};
    }
    obj[keys[idx]] = createNestObj(obj[keys[idx]], keys, val, ++idx);
    return obj;
}

// AAAKeyとAAAValでオブジェクトを作る
let data = createNestObj({}, AAAKey, AAAVal);
console.log(data);

// AABKeyとAABValでオブジェクトを生成してマージ ※第一引数には上記作成したオブジェクトのdata変数を利用
data = createNestObj(data, AABKey, AABVal);
console.log(data);

// BBBKeyとBBBValでオブジェクトを生成してマージ ※第一引数には上記作成したオブジェクトのdata変数を利用
data = createNestObj(data, BBBKey, BBBVal);
console.log(data);

// BCCKeyとBCCValでオブジェクトを生成してマージ ※第一引数には上記作成したオブジェクトのdata変数を利用
data = createNestObj(data, BCCKey, BCCVal);
console.log(data);

出力結果

  • 段階的にみるとマージされているのがわかる

1回目のログ

{
    keyA-0: {
        keyA-1: {
            keyA-2:"valAAA"
        }
    }
}

2回目のログ

{
    keyA-0: {
        keyA-1: {
            keyA-2:"valAAA",
            keyB-2:"valAAB"
        }
    }
}

3回目のログ

{
    keyA-0: {
        keyA-1: {
            keyA-2:"valAAA",
            keyB-2:"valAAB"
        }
    },
    keyB-0: {
        keyB-1: {
            keyB-2:"valBBB"
        }
    }
}

4回目のログ

{
    keyA-0: {
        keyA-1: {
            keyA-2:"valAAA",
            keyB-2:"valAAB"
        }
    },
    keyB-0: {
        keyB-1: {
            keyB-2:"valBBB"
        },
        keyC-1: {
            keyC-2:"valBCC"
        }
    }
}

結果について

  • 同じキーの値は同じオブジェクトに格納されている。
  • 重複したキー、値の場合は後勝ちで上書きされます。

reduceを使ってネストしたオブジェクトをマージしてみる

  • reduceを使いたいので、先ほどの値から以下のような配列をあらかじめ作った。
const keyValArr = [
    {
        key: 'keyA-0.keyA-1.keyA-2',
        val: 'valAAA',
    },
    {
        key: 'keyA-0.keyA-1.keyA-2',
        val: 'valAAB',
    },
    {
        key: 'keyB-0.keyB-1.keyB-2',
        val: 'valBBB',
    },
    {
        key: 'keyB-0.keyC-1.keyC-2',
        val: 'valBCC',
    },
];

/**
 * 先ほどの配列とまったく同じ
 * ネストしたオブジェクトを作成しながらマージ
 */
const createNestObj = (obj, keys, val, idx = 0) => { 
    if (keys.length === idx) return val;
    if (!obj[keys[idx]]) {
      obj[keys[idx]] = {};
    }
    obj[keys[idx]] = createNestObj(obj[keys[idx]], keys, val, ++idx);
    return obj;
}

// 配列を回しながらマージする。※reduceの初期値は空のオブジェクト 
const data = keyValArr.reduce((a, c) => {

  // ドットで区切って配列にする
  const cArr = c.key.split('.');

  // 再帰的にネストしたオブジェクトを呼び出す
  return createNestObj(a, cArr, c.val);

},{});

// 出力
console.log(data);

結果

スクリーンショット 2020-04-01 11 08 14