如何使用JavaScript或lodash从对象数组中删除不匹配的对象

9

我从服务器获取了两个对象数组,就像这样:

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' 
    }
]

我正在尝试从finalResult对象中仅获取不匹配的对象,最终结果将如下所示:
[
    { 
        reportDate: Fri Dec 14 2015 00:00:00 GMT+0530 (India Standard Time),
        testType: 'P1',
        productType: 'Flower',
        batchId: '145',
        licenseId: 'zzz',
        createType: 'DataUpload' 
    } 
]  

我正在尝试这个,但是没有得到正确的结果:
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);

2
什么构成了匹配? - Mulan
@naomik 我正在尝试将第一个对象的所有属性与第二个对象的属性进行匹配。 - Jeevan
我把你的代码放在了 https://jsfiddle.net/kjdx6e4o/ 上,看起来对我来说没问题。我有遗漏什么吗? - Dhananjay
@Dhananjay,小提琴正在工作。我不明白问题出在哪里。 - Kaushal Niraula
@Dhananjay,我从服务器获取了这两个对象。我看过你的代码,你把报告日期放在单引号里。但是我从服务器得到的结果没有包含报告日期的单引号,因为它是日期。 - Jeevan
1
@Jeevan 如果没有引号,数据就不是有效的JSON,也许需要在服务器上修复? - Dhananjay
5个回答

8
简单的方法
(注意:保留了HTML标签)
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' } ]

好的,它有效,但这大多是垃圾。它并不能完全准确地描述您试图进行比较的内容。它过于具体,一旦数据发生变化,它就会失效。

继续阅读,我们可以学到一些有趣的东西。


所有对象(键和值)的相等性……

我首先会制作几个通用程序,以便更好地描述解决问题的方法。

与其他解决方案相比,您会注意到这种解决方案不会对您数据的内部做任何假设。这种解决方案完全不关心您对象中实际使用的键名。

这意味着我们不会触及任何 batchIdlicenseIdreportDate。通用程序可以在这种情况下解决所有问题,最好的部分是您可以将它们一遍又一遍地应用于任何您想要处理的数据。

// 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}]

注意!

您需要对此解决方案进行一些调整,因为您没有比较所有的对象键。在finalResult中的对象具有比duplicateTestData中的对象更多的键,因此不存在任何1:1匹配。

简而言之,您希望将x = {a:1}y = {a:1,b:2}进行比较时被视为“匹配”,只要x中的所有键值都与y中的所有键值匹配即可。

如果我们使用上面的objectEquals比较器,finalResult中的对象不会被过滤掉,因为没有一个对象与duplicateTestData中的对象匹配。既然这不是您想要的结果,那么让我们定义一个适用于您情况的比较器……

// 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 的工作方式有些不同。我无法想到更好的名称,因为这是一种有点奇怪的比较方法。当 yundefined 时,这意味着该值的键不存在于“子集对象”中,因此不需要进行比较。

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

完整的工作示例

我已经附上了一个完整的片段,实际使用了你问题中包含的输入数据。在这里展开并运行它以查看效果。

// 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)


感谢 @naomik 抽出时间帮助我理解整个问题,非常感谢。 - Jeevan
@Jeevan,非常欢迎。如果您有任何问题,请务必告诉我。 - Mulan

6
var res = _.filter(finalResult, function(item) {
  return !_.find(duplicateTestData, {
    batchId: item.batchId,
    licenseId: item.licenseId,
    reportDate: item.reportDate
  });
});
console.log(res);

jsfiddle


对于Lodash用户来说,这是一个好的解决方案 +1 - 我建议使用_.find(...) === undefined而不是!._find(...),以便更加明确。 - Mulan

5

您可以使用哈希表并返回一个新的结果集,而不必在迭代数组时拼接。

function getKey(o) {
    return o.licenseId + '|' + o.reportDate + '|' + o.batchId;
}

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)' }],
    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' }],
    hash = Object.create(null),
    result = [];

duplicateTestData.forEach(function (a) {            
    hash[getKey(a)] = true;
});

result = finalResult.filter(function (a) {
    return !hash[getKey(a)];
});

console.log(result);

ES6

function getKey(o) {
    return o.licenseId + '|' + o.reportDate + '|' + o.batchId;
}

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)' }],
    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' }],
    map = duplicateTestData.reduce((r, a) => r.set(getKey(a)), new Map),
    result = finalResult.filter(a => !map.has(getKey(a)));

console.log(result);


2
为什么不在ES6解决方案中使用Map呢? - Mulan
1
(r.set(getKey(a)), r) 简化为 r.set(getKey(a)),使 Map 成为更好的选择 ^_^ - Mulan
我不知道对象的返回值。再次感谢。 - Nina Scholz

2
for (var a = 0; a < duplicateTestData.length; a++) {
  var dt = duplicateTestData[a];
  var dtr = new Date(dt.reportDate + '');

  for (var b = 0; b < finalResult.length; b++) {
    var fr = finalResult[b];
    var frr = new Date(fr.reportDate + '');

    //define your logic how to match two objects
    if (dtr.getTime() !== frr.getTime() &&
      dt.batchId !== fr.batchId) {
      //object matched. remove it from array

      var removed = finalResult.splice(b, 1);
      console.log('items removed', removed);
    }
  }
}

//print finalResult array
for (var c = 0; c < finalResult.length; c++) {
  console.log(finalResult[c]);
}

1
使用lodash:
duplicateTestData.reduce( _.reject, finalResult );

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' 
    }
]

console.log( duplicateTestData.reduce( _.reject, finalResult ) );
<script src="https://cdn.jsdelivr.net/lodash/4.15.0/lodash.min.js"></script>

这段内容的核心是_.reject(),它与_.filter()相反:当传递一个对象时,它将使用_.matches()进行部分比较。
要对duplicateTestData中的每个条目运行它,我们可以使用标准Array函数.reduce()。我们将finalResult作为initialValue传入。方便的是参数顺序正确,所以我们不需要一个匿名函数!(我应该真正地说一下,lodash是一个非常设计良好的库。)一旦reduce()遍历完duplicateTestData中的所有条目,它将返回最终过滤结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接