How to check if a key value pair (in an array) exists in another array?

Viewed 2082

I have two arrays, for example:

invoices: [
    {id: 90, Client: 'Bob', paid: false, total: 900},
    {id: 91, Client: 'Sarah', paid: false, total: 400}
]

and:

result: [{km: 200, hours: 20, Person: 'Sarah'}]

The invoices array is a JSON response from a GET and will sometimes be larger than result, so I in this case invoices has Bob while it doesnt exist in result.

How can I get the id of any objects whose Client does not appear as Person in result? I tried to do it with a double for loop but it didn't quite work out.

4 Answers

You can filter the invoices Array, using the result Array as this-argument and check for any Client in that (see MDN for Array.filter). Note, the filter-callback should be a normal function (i.e. not an arrow function) to be able to use the result-Array for thisArg.

console.log( `Id(s) not in result\n`, 
  [ {id: 90, Client: 'Bob', paid: false, total: 900}, 
    {id: 91, Client: 'Sarah', paid: false, total: 400},
    {id: 92, Client: 'Mia', paid: false, total: 200} ]
  .filter( 
    function(v) { return !this.find(n => n.Person === v.Client); }, 
    [{km: 200, hours: 20, Person: 'Sarah'}] // <= thisArg
   )
   .map(v => v.id) // <= fetch the found id's
   .join()
);
.as-console-wrapper { top: 0; max-height: 100% !important; }

By generating an object with keys which are the Person values from result, we can use that to efficiently filter the values in invoices, and then use map to return only the id values of the filtered result:

const invoices =  [
    {id: 90, Client: 'Bob', paid: false, total: 900},
    {id: 91, Client: 'Sarah', paid: false, total: 400}
]

const result =  [{km: 200, hours: 20, Person: 'Sarah'}]

const resindexes = result.reduce((c, o, i) =>
  (c[o.Person] = i, c), {});

ids = invoices.filter(o => resindexes[o.Client] === undefined).map(o => o.id);

console.log(ids);

Using Set and flatMap makes for an expressive, efficient solution. Set consumes an array and lets you check for members in constant time. flatMap lets you filter and map at the same time, in essence.

// Data
const invoices = [
  {id: 90, Client: 'Bob', paid: false, total: 900},
  {id: 91, Client: 'Sarah', paid: false, total: 400}
];
const personsArr = [{km: 200, hours: 20, Person: 'Sarah'}];

// Query
const persons = new Set(personsArr.map(r => r.Person));
const result = invoices.flatMap(i => !persons.has(i.Client) ? i.id : []);
console.log(result);

Or you can do it by make persons Map.

Plus of that attempt is you have access to person data by client name.

Notice I incremented invoices for 1 row for better result view.

// Data enter code here
const invoices = [
    {id: 90, Client: 'Bob', paid: false, total: 900},
    {id: 91, Client: 'Sarah', paid: false, total: 400},
    {id: 92, Client: 'Joe', paid: false, total: 400}
];
const personsArr = [{km: 200, hours: 20, Person: 'Sarah'}];

// Query
const persons = new Map(
    personsArr.map(r =>{ 
        return [r.Person, r]
    }
  )
);
// invoices which do not have results
const orphans = invoices.filter((i)=>!persons.has(i.Client));
// id of invoices which do not have results
const orphansId = orphans.map((o)=>o.id)

console.log(orphansId);

it returns: [ 90, 92 ]

Related