Ramda で入れ子のオブジェクトをフラットにする

Ramda を使って入れ子になったオブジェクトをフラットにする処理を考えてみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20200216/

サンプル1

まずは、以下のように入れ子になったオブジェクトを使って

{
    item: {
        name: 'item-1',
        details: {
            color: 'white',
            size: 'L'
        }
    },
    num: 5
}

下記のように変換してみます。

{ name: 'item-1', color: 'white', size: 'L', num: 5 }

方法はいろいろありそうですが、ここではオブジェクトの値部分を単一要素のオブジェクト {<項目名>: <値>} へ変換してから R.mergeAll でマージするようにしてみました。

sample1.js
const R = require('ramda')

const data = {
    item: {
        name: 'item-1',
        details: {
            color: 'white',
            size: 'L'
        }
    },
    num: 5
}

// Object の判定
const isObject = R.pipe(R.type, R.equals('Object'))

// 値部分を単一要素のオブジェクトへ変換
const valuesToObjOf = 
    R.pipe(
        R.mapObjIndexed(
            R.ifElse(
                isObject,
                v => valuesToObjOf(v), 
                R.flip(R.objOf)
            )
        ),
        R.values,
        R.flatten
    )

const flatObj = 
    R.pipe(
        valuesToObjOf,
        R.mergeAll
    )

console.log( valuesToObjOf(data) )

console.log('------')

console.log( flatObj(data) )

値部分がオブジェクトの場合は再帰的に処理するようにしていますが、その判定に R.is(Object) を使うと Array 等も true になってしまい都合が悪いので、R.type の結果が Object か否かで判定するようにしています。

実行結果
> node sample1.js

[ { name: 'item-1' }, { color: 'white' }, { size: 'L' }, { num: 5 } ]
------
{ name: 'item-1', color: 'white', size: 'L', num: 5 }

サンプル2

上記の処理だと項目名が重複した際に不都合が生じるため、項目名を連結するように valuesToObjOf を改良してみました。

sample2.js
・・・

const valuesToObjOf = (obj, prefix = '') =>
    R.pipe(
        R.mapObjIndexed(
            R.ifElse(
                isObject,
                (v, k) => valuesToObjOf(v, `${prefix}${k}_`), 
                (v, k) => R.objOf(`${prefix}${k}`, v)
            )
        ),
        R.values,
        R.flatten
    )(obj)

const flatObj = 
    R.pipe(
        valuesToObjOf,
        R.mergeAll
    )

console.log( flatObj(data) )
実行結果
> node sample2.js

{
  item_name: 'item-1',
  item_details_color: 'white',
  item_details_size: 'L',
  num: 5
}