Jeevan Jeevan - 3 months ago 14
Node.js Question

How to remove unmatched objects from array of objects using javascript or lodash

I am getting two array of objects from the server like this:

var duplicateTestData = [
{
licenseId: 'xxx',
batchId: '123',
reportDate: Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)
},
{
licenseId: 'yyy',
batchId: '124',
reportDate: Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)
},
{
licenseId: 'aaa',
batchId: '145',
reportDate: Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)
}
];

var finalResult = [
{
reportDate: Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time),
license: {},
testType: 'P1',
productType: 'Flower',
batchId: '123',
licenseId: 'xxx',
createType: 'DataUpload'
},
{
reportDate: Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time),
testType: 'P1',
productType: 'Flower',
batchId: '124',
licenseId: 'yyy',
createType: 'DataUpload'
},
{
reportDate: Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time),
testType: 'P1',
productType: 'Flower',
batchId: '145',
licenseId: 'aaa',
createType: 'DataUpload'
},
{
reportDate: Fri Dec 14 2015 00:00:00 GMT+0530 (India Standard Time),
testType: 'P1',
productType: 'Flower',
batchId: '145',
licenseId: 'zzz',
createType: 'DataUpload'
}
]


I am trying to fetch only unmatched objects from the
finalResult
object, the final result would be like this:

[
{
reportDate: Fri Dec 14 2015 00:00:00 GMT+0530 (India Standard Time),
testType: 'P1',
productType: 'Flower',
batchId: '145',
licenseId: 'zzz',
createType: 'DataUpload'
}
]


I am trying this, but am not getting the right result:

for(var j=0;j < duplicateTestData.length;j++){
for (var i = 0; i < finalResult.length; i++) {
if (
(finalResult[i].licenseId == duplicateTestData[j].licenseId) &&
(finalResult[i].reportDate == duplicateTestData[j].reportDate) &&
(finalResult[i].batchId == duplicateTestData[j].batchId)
) {
finalResult.splice(i, 1);
break;
}
}
}

console.log(finalResult);

Answer

the easy way out

finalResult.filter(({batchId:a, licenseId:b, reportDate:c}) =>
  duplicateTestData.find(({batchId:x, licenseId:y, reportDate:z}) =>
    a === x && b === y && c === z) === undefined)

=> [ { reportDate: 'Fri Dec 14 2015 00:00:00 GMT+0530 (India Standard Time)',
    testType: 'P1',
    productType: 'Flower',
    batchId: '145',
    licenseId: 'zzz',
    createType: 'DataUpload' } ]

OK, it works, but this is mostly garbage. It doesn't completely and accurately describe the comparison you're trying to make. It's way too specific and it will break as soon as something about your data changes.

Keep reading and we can learn something fun.


object equality for all (keys and values) …

I would begin by making several generic procedures that we can use to better describe the solution to our problem.

What you'll notice about this solution compared to others is that it makes no assumptions about the internals of your data. This solution couldn't care less about the actual key names used in your objects.

That means we won't be touching any batchId, licenseId, or reportDate. Generic procedures can solve everything in this case and the best part about that is you can use them over and over again on any data you wish to process.

// arrayCompare :: (a -> b -> Bool) -> [a] -> [b] -> Bool
const arrayCompare = f=> ([x,...xs])=> ([y,...ys])=> {
  if (x === undefined && y === undefined)
    return true
  else if (! f (x) (y))
    return false
  else
    return arrayCompare (f) (xs) (ys)
}

// keys :: Object(k:v) -> [k]
const keys = Object.keys

// objectCompare :: (v -> v -> Bool) -> Object(k:v) -> Object(k:v) -> Bool
const objectCompare = f=> a=> b=>
  arrayCompare (x=> y=> f (a[x]) (b[y]) && f (a[y]) (b[y])) (keys(a)) (keys(b))

// objectEqual :: Object -> Object -> Bool
const objectEqual = objectCompare (x=> y=> x === y)

// sample data
let xs = [
  {a:1,b:10},
  {a:2,b:20},
  {a:3,b:30}
]

let ys = [
  {a:1,b:10},
  {a:2,b:20},
  {a:3,b:30},
  {a:4,b:40}
]

// return all ys that are not present in xs
var result = ys.filter(y=> xs.find(objectEqual(y)) === undefined)

console.log(result)
// [{a:4,b:40}]

gotcha !

You will have to adjust this solution somewhat because you're not comparing all object keys. Objects in finalResult have more keys than objects in duplicateTestData so there are zero 1:1 matches.

In simpler words, you want x = {a:1} to be considered a "match" if it is compared against y = {a:1,b:2}, so long as all key:values in x match all key:values in y

If we used the objectEquals comparator above, nothing would be filtered out of finalResult because no object would match an object found in duplicateTestData. Since this is not what you want, let's define a comparator that works for your case …

// subsetObjectEquals :: Object -> Object -> Bool
const subsetObjectEquals = objectCompare (x=> y=> y === undefined || x === y)

// this time use subsetObjectEquals
var result = finalResult.filter(x=>
  duplicateTestData.find(subsetObjectEquals(x)) === undefined)

subsetObjectEquals works a bit differently. I couldn't really think of a better name because this is a somewhat strange comparison. When y is undefined, it means the key for that value is not present in the "subset object" and therefore does not need to be compared

subsetObjectEquals(a,b)
// returns true if all key:value pairs in `a` match all key:value pairs in `b`
// otherwise returns false

full working example

I've attached a full snippet that actually uses the input data included in your question. Expand it here and run it to see it work

// arrayCompare :: (a -> b -> Bool) -> [a] -> [b] -> Bool
const arrayCompare = f=> ([x,...xs])=> ([y,...ys])=> {
  if (x === undefined && y === undefined)
    return true
  else if (! f (x) (y))
    return false
  else
    return arrayCompare (f) (xs) (ys)
}

// keys :: Object(k:v) -> [k]
const keys = Object.keys

// objectCompare :: (v -> v -> Bool) -> Object(k:v) -> Object(k:v) -> Bool
const objectCompare = f=> a=> b=>
  arrayCompare (x=> y=> f (a[x]) (b[x]) && f (a[y]) (b[y])) (keys(a)) (keys(b))

// objectEqual :: Object -> Object -> Bool
const objectEqual = objectCompare (x=> y=> x === y)

// subsetObjectEquals :: Object -> Object -> Bool
const subsetObjectEquals = objectCompare (x=> y=> y === undefined || x === y)

// your data
var duplicateTestData = [{ licenseId: 'xxx',
    batchId: '123',
    reportDate: 'Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)' },
  { licenseId: 'yyy',
    batchId: '124',
    reportDate: 'Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)' },
  { licenseId: 'aaa',
    batchId: '145',
    reportDate: 'Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)' }
  ];

var finalResult = [ { reportDate: 'Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)',
    license: {},
    testType: 'P1',
    productType: 'Flower',
    batchId: '123',
    licenseId: 'xxx',
    createType: 'DataUpload' },
    { reportDate: 'Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)',
    testType: 'P1',
    productType: 'Flower',
    batchId: '124',
    licenseId: 'yyy',
    createType: 'DataUpload' },
    { reportDate: 'Fri Dec 11 2015 00:00:00 GMT+0530 (India Standard Time)',
    testType: 'P1',
    productType: 'Flower',
    batchId: '145',
    licenseId: 'aaa',
    createType: 'DataUpload' },
    { reportDate: 'Fri Dec 14 2015 00:00:00 GMT+0530 (India Standard Time)',
    testType: 'P1',
    productType: 'Flower',
    batchId: '145',
    licenseId: 'zzz',
    createType: 'DataUpload' }               
]

// get all finalResult items that do not subsetObjectEqual items in duplicateTestData
var result = finalResult.filter(x=>
  duplicateTestData.find(subsetObjectEquals(x)) === undefined)

console.log(result)