2

I am trying to take data from one array, group the data by object property, and then send it to a new array. The current data format is as such:

const data = [
    {
        "dataId": "1",
        "tableName": "table1",
        "column": "firstHeader",
        "rows": [
            "a","b","c"
        ]
    },
    {
        "dataId": "2",
        "tableName": "table1",
        "column": "secondHeader",
        "rows": [
            "d","e","f",
        ]
    },
    {
        "dataId": "3",
        "tableName": "table2",
        "column": "aNewFirstHeader",
        "rows": [
            1,2,3
        ]
    }
];

I used the Lodash groupby method, which groups the items in a format that is close to what I am expecting, but not exactly what I want my end result to be. The lodash result is:

[
    [
        { "tableName": 'table1',
            "dataId": '1',
            "header": 'firstHeader',
            "rows": ["a","b","c"]
        },

        { "tableName": 'table1',
            "dataId": '1',
            "header": 'secondHeader',
            "rows": ["d","e","f"]
        }
    ],

    [
        { "tableName": 'table2',
            "dataId": '2',
            "header": 'aNewFirstHeader',
            "rows": [1,2,3] },
    ]
]

Ultimately, what I am looking for is the end result of this:

[
    {
    "tableName": "table1",
    "column": ["firstHeader", "secondHeader"],
    "rows": [["a","b","c"], ["d","e","f"]]
    },
    {
    "tableName": "table2",
    "column": ["aNewFirstHeader"],
    "rows": [[1,2,3]]
    }
]

Is there a different way to use the groupBy method, or perhaps a javascript only approach that would get the end result I am looking for?

2
  • what is your grouping condition? Commented Jan 27, 2017 at 16:21
  • I used the following command to push data into a new array called groupedRows when using the Lodash groupBy command, which got me the result seen in the second code block. groupedRows.push(_.groupBy(data, "tableName" )) Commented Jan 27, 2017 at 18:37

9 Answers 9

2

By using reduce and findIndex, we can rebuild a new array which suits your format in O(n) time.

data.reduce((arr, record) => {
  const index = arr.findIndex((inside) => (inside.tableName === record.tableName));

  if (index === -1) { 
    arr.push({
        tableName: record.tableName,
        column: [record.column],
        rows: [record.rows]
    }) 
  } else {
    arr[index].column.push(record.column);
    arr[index].rows.push(record.rows);
  }

  return arr;
}, []);

const data = [
{
    "dataId": "1",
    "tableName": "table1",
    "column": "firstHeader",
    "rows": [
        "a", "b", "c"
    ]
},
{
    "dataId": "2",
    "tableName": "table1",
    "column": "secondHeader",
    "rows": [
        "d", "e", "f",
    ]
},
{
    "dataId": "3",
    "tableName": "table2",
    "column": "aNewFirstHeader",
    "rows": [
        1, 2, 3
    ]
}
];

const newData = data.reduce((arr, record) => {
  const index = arr.findIndex((inside) => (inside.tableName === record.tableName));

  if (index === -1) { 
	arr.push({
        tableName: record.tableName,
		column: [record.column],
		rows: [record.rows]
	}) 
  } else {
	arr[index].column.push(record.column);
    arr[index].rows.push(record.rows);
  }
  return arr;

}, []);

console.log(newData);

Sign up to request clarification or add additional context in comments.

2 Comments

Excellent answer- short and sweet, and readable to boot!. Thanks!
@unseen_damage no prob, thanks for allowing me to submit my first contribution to SO
0

Use the built-in JavaScript Array Prototype methods map and reduce to simplify your problem:

const data = [
    {
        "dataId": "1",
        "tableName": "table1",
        "column": "firstHeader",
        "rows": [
            "a", "b", "c"
        ]
    },
    {
        "dataId": "2",
        "tableName": "table1",
        "column": "secondHeader",
        "rows": [
            "d", "e", "f",
        ]
    },
    {
        "dataId": "3",
        "tableName": "table2",
        "column": "aNewFirstHeader",
        "rows": [
            1, 2, 3
        ]
    }
];

const EXPECTED = [
    {
        "tableName": "table1",
        "column": ["firstHeader", "secondHeader"],
        "rows": [["a", "b", "c"], ["d", "e", "f"]]
    },
    {
        "tableName": "table2",
        "column": ["aNewFirstHeader"],
        "rows": [[1, 2, 3]]
    }
];

var result = data
    .reduce((hash, dataItem) => {
        // first group by tableName
        if (hash[dataItem.tableName]) {
            hash[dataItem.tableName].column.push(dataItem.column);
            hash[dataItem.tableName].rows.push(dataItem.rows);
        } else {
            hash[dataItem.tableName] = {
                column: [dataItem.column],
                rows: [dataItem.rows]
            };
        }

        return hash;
    }, {});

// run a map to change to the exact array style you want
result = Object.keys(result).map((tableName) => {
    return {
        tableName: tableName,
        column: result[tableName].column,
        rows: result[tableName].rows
    };
});

// if in a node environment, you can assert (for unit testing only)
require('assert').deepStrictEqual(result, EXPECTED);

Comments

0

Try this , used plain vanilla js only:

var merge = function( data){
        var outputArray = [], enteredNames = [];
        for(var i=0; i<data.length; i++){
            var datum = data[i];
            var ownElement;
            if(enteredNames.indexOf(datum.tableName)>-1)
                ownElement = outputArray[enteredNames.indexOf(datum.tableName)];
            else
                {
                ownElement = {

                    "tableName": datum.tableName,
                    "column": [],
                    "rows": []

                    }
                enteredNames.push( datum.tableName ); 
                outputArray.push( ownElement );
                }

            ownElement.column.push( datum.column );
            ownElement.rows.push( datum.rows );

        }
        return outputArray;
}

Comments

0

jsFiddle

const data = [
    {
        "dataId": "1",
        "tableName": "table1",
        "column": "firstHeader",
        "rows": [
            "a","b","c"
        ]
    },
    {
        "dataId": "2",
        "tableName": "table1",
        "column": "secondHeader",
        "rows": [
            "d","e","f",
        ]
    },
    {
        "dataId": "3",
        "tableName": "table2",
        "column": "aNewFirstHeader",
        "rows": [
            1,2,3
        ]
    }
];

var result = _.chain(data)
    .groupBy("tableName")
    .pairs()
    .map(function (currentItem) {
        // used to create desired properties
        var rows = [];
        var columns = [];
        // loop to fill values
        for(var i=0,length=currentItem[1].length;i<length;i++){
          columns.push(currentItem[1][i].column)
          rows = rows.concat(currentItem[1][i].rows);
        }
        return {
          'tableName': currentItem[0],
          'column': columns,
          'rows': rows
        };
    })
    .value();
console.log(result);

Comments

0

After using _.groupBy() map each group using _.mergeWith() to combine all objects in the group to a new object, and push everything, except tableName into an array. Then use _.omit() to remove dataId:

const data = [{"dataId":"1","tableName":"table1","column":"firstHeader","rows":["a","b","c"]},{"dataId":"2","tableName":"table1","column":"secondHeader","rows":["d","e","f"]},{"dataId":"3","tableName":"table2","column":"aNewFirstHeader","rows":[1,2,3]}];

const result = _(data)
  .groupBy('tableName')
  .map((group) => // map each group
       // remove the dataId property
    _.omit( 
      // merge all group objects
      _.mergeWith({}, ...group, (objValue = [], srcValue, key) => {
        // if the key is not tableName push it into the array
        if (key !== 'tableName') {
          objValue.push(srcValue);
          return objValue;
        }
      }),
      'dataId'))
  .value();

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

Comments

0

Plain Javascript:

function groupBy(data, fieldName) {
    var groups = {}
    for (var i=0; i<data.length; i++) {
        var elem = data[i];
        if (fieldName in elem) {
            var fieldVal = elem[fieldName];
            if (!(fieldVal in groups)) {
                groups[fieldVal] = [];
            }
            groups[fieldVal].push(elem)
        }
    }
    var result = [];
    for (list in groups) {
        var arr = groups[list];
        var combine = {}
        combine[fieldName] = arr[0][fieldName];
        for (var i=0; i<arr.length; i++) {
            var elem = arr[i];
            for (prop in elem) {
                if (prop == fieldName) {
                    continue;
                }
                if (!(prop in combine)) {
                    combine[prop] = [];
                }
                combine[prop].push(elem[prop]);
            }
        }
        result.push(combine);
    }
    return result;
}

For your case, simply invoke:

groupBy(data, "tableName")

Comments

0

In plain Javascript, you could use Map

var data = [{ dataId: "1", tableName: "table1", column: "firstHeader", rows: ["a", "b", "c"] }, { dataId: "2", tableName: "table1", column: "secondHeader", rows: ["d", "e", "f", ] }, { dataId: "3", tableName: "table2", column: "aNewFirstHeader", rows: [1, 2, 3] }],
    result = data.reduce((map => (r, a) => {
        var item = map.get(a.tableName);
        if (!item) {
            item = { tableName: a.tableName, column: [], rows: [] }
            map.set(a.tableName, item);
            r.push(item);
        }
        item.column.push(a.column);
        item.rows.push(a.rows);
        return r;
    })(new Map), []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Comments

0

Edit: Oh, I totally forgot that .findIndex() is a thing. Here is the updated solution:

Use .map(), .reduce() & .findIndex():

const newArr = data.map(value => ({
        tableName: value.tableName,
        column: [value.column],
        rows: [value.rows],
    }))
    .reduce((acc, cur) => {
        const tableNameIndex = acc.findIndex(el => el.tableName === cur.tableName);
        if (tableNameIndex !== -1) {
            acc[tableNameIndex].column.push(cur.column[0]);
            acc[tableNameIndex].rows.push(cur.rows[0]);
            return acc;
        } else {
            acc.push(cur);
            return acc;
        }
    }, [])

console.log(newArr);

Comments

0

Using ES6 with unique key approach:

"Way of the future" :-)

http://es6-features.org/

const desiredResults = [{
      "dataId": "1",
      "tableName": "table1",
      "column": "firstHeader",
      "rows": [
        "a", "b", "c"
      ]
    },
    {
      "dataId": "2",
      "tableName": "table1",
      "column": "secondHeader",
      "rows": [
        "d", "e", "f",
      ]
    },
    {
      "dataId": "3",
      "tableName": "table2",
      "column": "aNewFirstHeader",
      "rows": [
        1, 2, 3
      ]
    }
  ]
  .reduce((unqiueMap, {
    dataId, // Destructuring each data element
    tableName,
    column,
    rows
  }) => {
    if (tableName in unqiueMap) {
      unqiueMap[tableName].column.push(column);
      unqiueMap[tableName].rows.push(rows);
    } else {
      unqiueMap[tableName] = {
        column: [column],
        rows: [rows]
      }
    }
    return unqiueMap;
  }, {})

console.log(desiredResults)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.