1

I have an array of Javascript objects like below.

        [{

            "email": "alex@test.com",
            "fn": "Alex",
            "sn": "McPherson",
            "phone": "01233xxxxx",
            "hours": "40",
            "rate": "20",
            "amount": "200",
            "vat": "60",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        },
        {

            "email": "mike@test.com",
            "fn": "Mike",
            "sn": "Mann",
            "phone": "01233xxxxx",
            "hours": "50",
            "rate": "70",
            "amount": "500",
            "vat": "90",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        },
        {

            "email": "fred@test.com",
            "fn": "Fred",
            "sn": "Frogg",
            "phone": "01233xxxxx",
            "hours": "80",
            "rate": "90",
            "amount": "800",
            "vat": "100",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        },
        {

            "email": "alex@test.com",
            "fn": "Alex",
            "sn": "McPherson",
            "phone": "01233xxxxx",
            "hours": "90",
            "rate": "30",
            "amount": "900",
            "vat": "120",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        }]

What I ideally want is to group those of the same value (email) into there own sub array of objects i.e if you look at the array above you will see I have 2 entries for the same person Alex McPherson. What I want to do is the below if possible move and combine into a sub array and the same for any other value that exists more than once.

    [[{

        "email": "alex@test.com",
        "fn": "Alex",
        "sn": "McPherson",
        "phone": "01233xxxxx",
        "hours": "40",
        "rate": "20",
        "amount": "200",
        "vat": "60",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    },{

        "email": "alex@test.com",
        "fn": "Alex",
        "sn": "McPherson",
        "phone": "01233xxxxx",
        "hours": "90",
        "rate": "30",
        "amount": "900",
        "vat": "120",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    }],
    [{

        "email": "mike@test.com",
        "fn": "Mike",
        "sn": "Mann",
        "phone": "01233xxxxx",
        "hours": "50",
        "rate": "70",
        "amount": "500",
        "vat": "90",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    }],
    [{

        "email": "fred@test.com",
        "fn": "Fred",
        "sn": "Frogg",
        "phone": "01233xxxxx",
        "hours": "80",
        "rate": "90",
        "amount": "800",
        "vat": "100",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    }]]

I can't seem to get my head around resorting the array.

Alex McPherson
  • 3,185
  • 3
  • 30
  • 41
  • 1
    Are you wanting to sort/combine by only one specific property in each object in the array, or by any that match? – Clonkex Jun 08 '17 at 11:00
  • Possible duplicate of [What is the most efficient method to groupby on a javascript array of objects?](https://stackoverflow.com/questions/14446511/what-is-the-most-efficient-method-to-groupby-on-a-javascript-array-of-objects) – Jack Evans Jun 08 '17 at 11:01
  • What have you already tried? I don't see any JavaScript trying to actually do anything. – zer00ne Jun 08 '17 at 11:02
  • I want to combine into a subarray based on the email value. So for instance if an email value exists more than once in each object take all those subsequent objects then combine into that sub array just like the example above. – Alex McPherson Jun 08 '17 at 11:02
  • @zer00ne I have tried several methods. I am not going to post code that doesn't work hence the question. – Alex McPherson Jun 08 '17 at 11:05
  • @AlexMcPherson That's how SO works, OP provides the code that doesn't work and then we fix it. 1.7k rep I'm sure you are familiar with this policy? – zer00ne Jun 08 '17 at 11:09
  • @zer00ne yes I am familiar with the rules. To be honest my code was a mess I was more struggling with the order of events. Rather than bash people either help with constructive criticism rather than show how great you are at picking up on things that don't help anyone. If you had said I wont provide you code but the order of events should be X that would be more helpful. I guess you'll increase your rep for bashing rather than helping so if it bothers you that much downvote it or pick up on a spelling mistake if it makes your day better ;-) – Alex McPherson Jun 08 '17 at 11:17
  • @AlexMcPherson You said, *" I guess you'll increase your rep for bashing rather than helping so if it bothers you that much downvote it or pick up on a spelling mistake if it makes your day better "* Whatever *"bashing"* I bestowed upon you (real or imagined (*in reality it's imaged*)) did not gain me any points, nor have I proofread anything. Why would I put forth anymore effort to helping you when it's clearly plain to see that you will not put any effort in helping me to help you. If I was to truly *"bash"* you, I would say that you're lazy and thin skinned, but I didn't. ;-) – zer00ne Jun 08 '17 at 22:05

6 Answers6

3

You could use a closure over a hash table for the same email address and their items.

var data = [{ email: "alex@test.com", fn: "Alex", sn: "McPherson", phone: "01233xxxxx", hours: "40", rate: "20", amount: "200", vat: "60", agency: "test", start: "08/06/2017", end: "10/06/2017" }, { email: "mike@test.com", fn: "Mike", sn: "Mann", phone: "01233xxxxx", hours: "50", rate: "70", amount: "500", vat: "90", agency: "test", start: "08/06/2017", end: "10/06/2017" }, { email: "fred@test.com", fn: "Fred", sn: "Frogg", phone: "01233xxxxx", hours: "80", rate: "90", amount: "800", vat: "100", agency: "test", start: "08/06/2017", end: "10/06/2017" }, { email: "alex@test.com", fn: "Alex", sn: "McPherson", phone: "01233xxxxx", hours: "90", rate: "30", amount: "900", vat: "120", agency: "test", start: "08/06/2017", end: "10/06/2017" }],
    result = data.reduce(function (hash) {
        return function (r, o) {
            if (!hash[o.email]) {
                hash[o.email] = [];
                r.push(hash[o.email]);
            }
            hash[o.email].push(o)
            return r;
        };
    }(Object.create(null)), []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Glad you answered, I was realising my attempt at an answer was way too naive to be good :P – Clonkex Jun 08 '17 at 11:06
  • @Nina Scholz perfect exactly what I was after. I had thought about reduce but didnt quite know how to implement it. This is great I will accept the answer :-) – Alex McPherson Jun 08 '17 at 11:10
2

Hard to be more efficient than something like this:

const groupBy = (data, groupingProp) => {
  // Create a dictionnary of groups.
  const map = new Map();
  data.forEach(entry => {
    const groupKey = entry[groupingProp];
    const list = map.get(groupKey);
    if (list == null) {
      map.set(groupKey, [entry]);
    } else {
      list.push(entry);
    }
  });
  // Transform it back into a list of groups.
  return [...map.values()];
}

// The data.
const data = [{
    "email": "alex@test.com",
    "fn": "Alex",
    "sn": "McPherson",
    "phone": "01233xxxxx",
    "hours": "40",
    "rate": "20",
    "amount": "200",
    "vat": "60",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
  },
  {
    "email": "mike@test.com",
    "fn": "Mike",
    "sn": "Mann",
    "phone": "01233xxxxx",
    "hours": "50",
    "rate": "70",
    "amount": "500",
    "vat": "90",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
  },
  {
    "email": "fred@test.com",
    "fn": "Fred",
    "sn": "Frogg",
    "phone": "01233xxxxx",
    "hours": "80",
    "rate": "90",
    "amount": "800",
    "vat": "100",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
  },
  {
    "email": "alex@test.com",
    "fn": "Alex",
    "sn": "McPherson",
    "phone": "01233xxxxx",
    "hours": "90",
    "rate": "30",
    "amount": "900",
    "vat": "120",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
  }
];

console.log(groupBy(data, 'email'));
Quentin Roy
  • 7,677
  • 2
  • 32
  • 50
1

You could use an ES6 map to collect the data per email and then extract the resulting values from that map (assuming data is input):

Array.from(
    data.reduce( 
        (acc, o) => (acc.get(o.email).push(o), acc),
        new Map(data.map( o => [o.email, []] ))
    ), ([key, value]) => value
)

var data = [{
    "email": "alex@test.com",
    "fn": "Alex",
    "sn": "McPherson",
    "phone": "01233xxxxx",
    "hours": "40",
    "rate": "20",
    "amount": "200",
    "vat": "60",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
}, {
    "email": "mike@test.com",
    "fn": "Mike",
    "sn": "Mann",
    "phone": "01233xxxxx",
    "hours": "50",
    "rate": "70",
    "amount": "500",
    "vat": "90",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
}, {
    "email": "fred@test.com",
    "fn": "Fred",
    "sn": "Frogg",
    "phone": "01233xxxxx",
    "hours": "80",
    "rate": "90",
    "amount": "800",
    "vat": "100",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
}, {
    "email": "alex@test.com",
    "fn": "Alex",
    "sn": "McPherson",
    "phone": "01233xxxxx",
    "hours": "90",
    "rate": "30",
    "amount": "900",
    "vat": "120",
    "agency": "test",
    "start": "08/06/2017",
    "end": "10/06/2017"
}];

var result = Array.from(
    data.reduce( 
        (acc, o) => (acc.get(o.email).push(o), acc),
        new Map(data.map( o => [o.email, []] ))
    ), ([key, value]) => value
);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
trincot
  • 317,000
  • 35
  • 244
  • 286
0

Here is working example. We use ES6 array .reduce() function.

      var data = [{

            "email": "alex@test.com",
            "fn": "Alex",
            "sn": "McPherson",
            "phone": "01233xxxxx",
            "hours": "40",
            "rate": "20",
            "amount": "200",
            "vat": "60",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        },
        {

            "email": "mike@test.com",
            "fn": "Mike",
            "sn": "Mann",
            "phone": "01233xxxxx",
            "hours": "50",
            "rate": "70",
            "amount": "500",
            "vat": "90",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        },
        {

            "email": "fred@test.com",
            "fn": "Fred",
            "sn": "Frogg",
            "phone": "01233xxxxx",
            "hours": "80",
            "rate": "90",
            "amount": "800",
            "vat": "100",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        },
        {

            "email": "alex@test.com",
            "fn": "Alex",
            "sn": "McPherson",
            "phone": "01233xxxxx",
            "hours": "90",
            "rate": "30",
            "amount": "900",
            "vat": "120",
            "agency": "test",
            "start": "08/06/2017",
            "end": "10/06/2017"
        }]
        
        
        const grouped = data.reduce((previous, current) => {
          if (!previous[current.email]) {
            const found = previous.find(element => element.email === current.email);
            if (found) {
              const index = previous.indexOf(found);
              previous.splice(index,1);
              // We add always at the top
              previous.unshift([found, current]);
            } else {
              previous.push(current);
            }
          }
          
          return previous;
        }, [])
        
        console.log(grouped);
      
.as-console-wrapper { max-height: 100% !important; top: 0; }
loelsonk
  • 3,570
  • 2
  • 16
  • 23
0
        let emails = [{

        "email": "alex@test.com",
        "fn": "Alex",
        "sn": "McPherson",
        "phone": "01233xxxxx",
        "hours": "40",
        "rate": "20",
        "amount": "200",
        "vat": "60",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    },
    {

        "email": "mike@test.com",
        "fn": "Mike",
        "sn": "Mann",
        "phone": "01233xxxxx",
        "hours": "50",
        "rate": "70",
        "amount": "500",
        "vat": "90",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    },
    {

        "email": "fred@test.com",
        "fn": "Fred",
        "sn": "Frogg",
        "phone": "01233xxxxx",
        "hours": "80",
        "rate": "90",
        "amount": "800",
        "vat": "100",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    },
    {

        "email": "alex@test.com",
        "fn": "Alex",
        "sn": "McPherson",
        "phone": "01233xxxxx",
        "hours": "90",
        "rate": "30",
        "amount": "900",
        "vat": "120",
        "agency": "test",
        "start": "08/06/2017",
        "end": "10/06/2017"
    }];

    let emails_obj = [];
    let output_array = [];

    emails.forEach(function(obj) {
        if (emails_obj[obj.email] == undefined) {
            emails_obj[obj.email] = []
            emails_obj[obj.email].push(obj);
        } else {
            emails_obj[obj.email].push(obj);
        }
    })        

    for (var key in emails_obj) {
        output_array.push(emails_obj[key]);
    }
    console.log(output_array);
Wahbigbig
  • 70
  • 5
0

Instead of DIY implementation. You could use ramda groupWith

Simply

R.groupWith((a ,b) => a.email === b.email, emails)
kkpoon
  • 1,939
  • 13
  • 23