Sunday 13 December 2020

Join JavaScript Arrays

The other day I needed to join values from 2 javascript arrays, same as if I were joining values from 2 Database tables. In C# we can join values in collections by means of Linq to Objects and its Join method. In JavaScript I'm not aware of Lodash providing something similar, but it's pretty simple to implement, so that's what I've done.

The most important thing is what should I return in the resulting array after joining the 2 collections so that it's easy to apply on it .map (select) and .filter(where). The best option that came to mind was putting each joined pair in an object with 2 keys, with each key provided as a sort of "alias" in the call to join. OK, I better show the code:


function joinCollections(collection1, collection2, idSelectorFn1, idSelectorFn2, alias1, alias2){
    let result = [];
    collection1.forEach(it1 => {
        let id1 = idSelectorFn1(it1);
        collection2.forEach(it2 =>{
            if (id1 === idSelectorFn2(it2))
                result.push({
                    [alias1]: it1,
                    [alias2]: it2
                });
        });
    });
    return result;
}

With that, we can easily access the 2 objects being joined for each "row" in order to do additional filtering or for mapping into a single resulting object.

Given the following inputs:


let employees = [
    { 
        firstName: "Terry", 
        lastName: "Adams", 
        role: "FrontEnd Developer"
    },
    { 
        firstName:"Charlotte", 
        lastName:"Weiss", 
        role: "Systems Administrator" 
    },
    { 
        firstName:"Magnus", 
        lastName:"Hedland", 
        role: "Psycologist"
    }, 
    { 
        firstName:"Vernette", 
        lastName:"Price", 
        role: "Shop Assistant"
    }
];

var students = [
    { 
        firstName:"Vernette", 
        lastName:"Price", 
        studies: "Philosophy"
    },
    { 
        firstName:"Terry", 
        lastName:"Earls", 
        studies: "Computer Engineering"
     },
     { 
         firstName:"Terry", 
         lastName:"Adams", 
         studies: "Computer Engineering"
    } 
];

We can obtain a list of Person objects with their full name, job and studies.


let persons = joinCollections(employees, 
    students, 
    employee => `${employee.firstName}-${employee.lastName}`, 
    student => `${student.firstName}-${student.lastName}`,
    "employee",
    "student"
).map(ob => {
    return {
        fullName: `${ob.employee.firstName} ${ob.employee.lastName}`,
        job: ob.employee.role,
        studies: ob.student.studies
    };
});

console.log("-- persons:\n " + JSON.stringify(persons, null, "\t"));

let persons2 = joinCollections(employees, 
    students, 
    employee => `${employee.firstName}-${employee.lastName}`, 
    student => `${student.firstName}-${student.lastName}`,
    "employee",
    "student"
)
.filter(ob => ob.employee.role === "FrontEnd Developer")
.map(ob => {
    return {
        fullName: `${ob.employee.firstName} ${ob.employee.lastName}`,
        job: ob.employee.role,
        studies: ob.student.studies
    };
});

Different from what we do in SQL, if we want to join more than 2 collections, we should join 2 of them, map the resulting pairs to a normal object, and then do the next join. I mean, given a third collection with salaries data, to join it to the previous employees and students data we first should do the join and map done in the previous step, and then do an additional join, like this:


let salaries = [
    {
        job: "FrontEnd Developer",
        amount: 40000 
    },
    {
        job: "Shop Assistant",
        amount: 20000 
    }
];

let persons = joinCollections(employees, 
    students, 
    employee => `${employee.firstName}-${employee.lastName}`, 
    student => `${student.firstName}-${student.lastName}`,
    "employee",
    "student"
).map(ob => {
    return {
        fullName: `${ob.employee.firstName} ${ob.employee.lastName}`,
        job: ob.employee.role,
        studies: ob.student.studies
    };
});

let personsWithSalaries = joinCollections(
    persons,
    salaries,
    person => person.job, 
    salary => salary.job,
    "person",
    "salary"
).map(ob => {
    return {...ob.person, ...ob.salary};
});

As usual, I've uploaded it into a gist

No comments:

Post a Comment