Search code examples
javascriptcoding-stylelodash

Extracting key value from object into array of objects with specific fields


So I have this data:

fields = ['a', 'b', 'c']
data = [{r: 1, a: 2, b: 3, c: 4, h: 5}, {r: 4, a: 9, b: 1, c: 4, h: 5} ... ]

and I want to be able (preferred with lodash) to be able to get to this:

newData = [{r:1, h:5, values: [{name: 'a', value: 2},{name: 'b', value: 3}, {name: 'c', value: 4}], .....]

Meaning only the fields from the 'fields' object be taken out of each object in array (they always exist) and put into 'values' property that has an array of them in the format displayed here.

Would love to hear suggestions of the cleanest way to achieve this!

I did this :

function something(data, fields) {

  const formattedData = _.map(data, (currData) => {

    const otherFields = _.omit(currData, fields)

    return { 
      ...otherFields, 
      values: _.flow( 
        currData => _.pick(currData, fields), 
        pickedFields => _.toPairs(pickedFields), 
        pairs => _.map(pairs, pair => { 
          return { name: pair[0], value: pair[1] } 
        })
      )(currData)
    }
  })

  return formattedData
}

which works, but I'm wondering if it isn't a bit complicated.


Solution

  • The _.flow() method creates a function, which you can extract and name. In addition, the 1st function in the flow, accepts more than 1 parameter, so you don't need to pass it explicitly. Since _.toPairs() is unary, you don't need to wrap it in an arrow function. The object creation is a bit annoying. I've used _.zipObject(), but it's still cumbersome.

    Now you can use the function create by _.flow() in your main function, and it's pretty readable:

    const { flow, pick, toPairs, map, partial, zipObject, omit } = _
    
    const propsToObjs = flow(
      pick,
      toPairs,
      pairs => map(pairs, partial(zipObject, ['name', 'value'])),
    )
    
    const fn = (data, fields) =>
      map(data, currData => ({
        ...omit(currData, fields), 
        values: propsToObjs(currData, fields)
      }))
      
    const fields = ['a', 'b', 'c']
    const data = [{r: 1, a: 2, b: 3, c: 4, h: 5}, {r: 4, a: 9, b: 1, c: 4, h: 5}]
    
    const result = fn(data, fields)
    
    console.log(result)
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>

    Using lodash/fp, we can make the flow function even nicer, since lodash/fp functions are auto-curried and iteratee-first data-last (not the reversed order of parameters):

    const { flow, pick, toPairs, map, partial, zipObject, omit } = _
    
    const propsToObjs = flow(
      pick,
      toPairs,
      map(zipObject(['name', 'value']))
    )
    
    const fn = fields => map(currData => ({
      ...omit(fields, currData), 
      values: propsToObjs(fields, currData)
    }))
      
    const fields = ['a', 'b', 'c']
    const data = [{r: 1, a: 2, b: 3, c: 4, h: 5}, {r: 4, a: 9, b: 1, c: 4, h: 5}]
    
    const result = fn(fields)(data)
    
    console.log(result)
    <script src='https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)'></script>