从JSON数组中删除重复对象

51

我想在HTML中创建一系列表单,其格式为:

<div>
    <h3>Math K</h3>
    <li>Counting & Cardinality</li>
    <li>Geometry</li>
</div>
<div>
    <h3>Math 1</h3>
    <li>Counting & Cardinality</li>
    <li>Orders of Operation</li>
</div>
<div>
    <h3>Math 2</h3>
    <li>Geometry</li>
</div>

我最初的想法是创建一个数组,然后用$("#divid").append(array)将其推送到页面上的<div>元素中。我创建了一个看起来像这样的数组:

var standardsList = [
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Geometry"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Orders of Operation"},
    {"Grade": "Math 2", "Domain": "Geometry"},
    {"Grade": "Math 2", "Domain": "Geometry"}
];

我需要删除重复项,以便保留类似这样的内容:

var standardsList = [
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Geometry"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Orders of Operation"},
    {"Grade": "Math 2", "Domain": "Geometry"}
];

我尝试过安装 underscore.js 并使用 ._uniq,但似乎只有在对象中出现单个的 key:value 对时才有效。我似乎无法使其在多个键上起作用。

当我尝试以下操作时:

var uniqueStandards = _.uniq(standardsList, function(item, key, Domain){
    return item.Domain;
});

我只得到每个年级的前三个唯一值。但我需要在年级和领域之间获得所有唯一值。有没有一种简单的方法将两个键传递给_.uniq函数?

最终,我需要一个列表,其中每个唯一年级都作为标题,唯一领域作为列表项,以传递到HTML页面。


2
返回 item.Grade + item.Domain。 - cookie monster
1
可能是[Underscore:删除具有某些重复属性的对象]的重复问题(http://stackoverflow.com/questions/21426994/underscore-remove-object-having-some-duplicate-properties) - cookie monster
对于那些像我一样在寻找JSON去重工具的人,这个使用哈希表查找和数组.filter()提供了如此好的资源 -> https://www.jstips.co/en/javascript/deduplicate-an-array/ - Kzqai
19个回答

42

我知道已经有很多答案了,但是对于一个复杂的json结构,最适合我的答案是:

var arr = [{ "State": "RWCW", "desc": "WEST", "code": "RWCW", "level": 0, "save": "RWCW : WEST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RCSW", "desc": "SOUTHWEST", "code": "RCSW", "level": 0, "save": "RCSW : SOUTHWEST", "attribute1": "", "attribute2": "" }, { "State": "RECW", "desc": "NORTHEAST", "code": "RECW", "level": 0, "save": "RECW : NORTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RWCW", "desc": "WEST", "code": "RWCW", "level": 0, "save": "RWCW : WEST", "attribute1": "", "attribute2": "" }, { "State": "RWCW", "desc": "WEST", "code": "RWCW", "level": 0, "save": "RWCW : WEST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RCSW", "desc": "SOUTHWEST", "code": "RCSW", "level": 0, "save": "RCSW : SOUTHWEST", "attribute1": "", "attribute2": "" }, { "State": "RECW", "desc": "NORTHEAST", "code": "RECW", "level": 0, "save": "RECW : NORTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RWCW", "desc": "WEST", "code": "RWCW", "level": 0, "save": "RWCW : WEST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RECW", "desc": "NORTHEAST", "code": "RECW", "level": 0, "save": "RECW : NORTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RWCW", "desc": "WEST", "code": "RWCW", "level": 0, "save": "RWCW : WEST", "attribute1": "", "attribute2": "" }, { "State": "RCSW", "desc": "SOUTHWEST", "code": "RCSW", "level": 0, "save": "RCSW : SOUTHWEST", "attribute1": "", "attribute2": "" }, { "State": "RWCW", "desc": "WEST", "code": "RWCW", "level": 0, "save": "RWCW : WEST", "attribute1": "", "attribute2": "" }, { "State": "RCNW", "desc": "MIDWEST", "code": "RCNW", "level": 0, "save": "RCNW : MIDWEST", "attribute1": "", "attribute2": "" }, { "State": "RCSW", "desc": "SOUTHWEST", "code": "RCSW", "level": 0, "save": "RCSW : SOUTHWEST", "attribute1": "", "attribute2": "" }, { "State": "RECW", "desc": "NORTHEAST", "code": "RECW", "level": 0, "save": "RECW : NORTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RCNW", "desc": "MIDWEST", "code": "RCNW", "level": 0, "save": "RCNW : MIDWEST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RECW", "desc": "NORTHEAST", "code": "RECW", "level": 0, "save": "RECW : NORTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RCNW", "desc": "MIDWEST", "code": "RCNW", "level": 0, "save": "RCNW : MIDWEST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RCNW", "desc": "MIDWEST", "code": "RCNW", "level": 0, "save": "RCNW : MIDWEST", "attribute1": "", "attribute2": "" }, { "State": "RSCW", "desc": "SOUTHEAST", "code": "RSCW", "level": 0, "save": "RSCW : SOUTHEAST", "attribute1": "", "attribute2": "" }, { "State": "RECW", "desc": "NORTHEAST", "code": "RECW", "level": 0, "save": "RECW : NORTHEAST", "attribute1": "", "attribute2": "" }];

var clean = arr.filter((arr, index, self) =>
    index === self.findIndex((t) => (t.save === arr.save && t.State === arr.State)))

console.log(clean);

您可以直接在Chrome浏览器控制台中尝试此方法,并根据需要进行编辑。
希望这能帮助到某些人。

1
太棒了!我认为使用内置函数比使用foreach更有效率。 :) - sameera madushan

29
function arrUnique(arr) {
    var cleaned = [];
    arr.forEach(function(itm) {
        var unique = true;
        cleaned.forEach(function(itm2) {
            if (_.isEqual(itm, itm2)) unique = false;
        });
        if (unique)  cleaned.push(itm);
    });
    return cleaned;
}

var standardsList = arrUnique(standardsList);

FIDDLE

这将返回

var standardsList = [
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Geometry"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Orders of Operation"},
    {"Grade": "Math 2", "Domain": "Geometry"}
];

这正是你所要求的吗?


2
如果我有一个至少包含500个JSON对象,每个对象有10个属性的长列表,你对性能部分有什么建议? - maddy

20
最终,我需要一个列表,其中每个唯一的等级是标题,唯一的域名是列表项,以传递到HTML页面中。如果有更简单的方法来实现这个目标,我也很乐意听取意见。
所以实际上你不需要按照你提出的格式输出数组。
在这种情况下,我会采用非常简单和高效的解决方案:
var grades = {};
standardsList.forEach( function( item ) {
    var grade = grades[item.Grade] = grades[item.Grade] || {};
    grade[item.Domain] = true;
});

console.log( JSON.stringify( grades, null, 4 ) );

生成的 grades 对象为:
{
    "Math K": {
        "Counting & Cardinality": true,
        "Geometry": true
    },
    "Math 1": {
        "Counting & Cardinality": true,
        "Orders of Operation": true
    },
    "Math 2": {
        "Geometry": true
    }
}

这种方法的一个有趣之处在于其非常快速。请注意,与其他需要多次通过输入数组的解决方案(无论是自己编写还是使用_.uniq())相比,它仅对输入数组进行了一次遍历。对于少量项来说,这并不重要,但对于更大的列表来说,保持这一点很好。

有了这个对象,您现在拥有运行任何代码或生成任何其他格式所需的一切。例如,如果您确实需要您提到的精确数组输出格式,则可以使用:

var outputList = [];
for( var grade in grades ) {
    for( var domain in grades[grade] ) {
        outputList.push({ Grade: grade, Domain: domain });
    }
}

JSON.stringify( outputList, null, 4 );

这将会记录:
[
    {
        "Grade": "Math K",
        "Domain": "Counting & Cardinality"
    },
    {
        "Grade": "Math K",
        "Domain": "Geometry"
    },
    {
        "Grade": "Math 1",
        "Domain": "Counting & Cardinality"
    },
    {
        "Grade": "Math 1",
        "Domain": "Orders of Operation"
    },
    {
        "Grade": "Math 2",
        "Domain": "Geometry"
    }
]

Rai在评论中问这行代码如何工作:

var grade = grades[item.Grade] = grades[item.Grade] || {};

这是一种常见的用法,用于获取对象属性或在该属性不存在时提供默认值。请注意=赋值是从右向左执行的。因此,我们可以通过使用if语句和临时变量来逐字翻译它:

// Fetch grades[item.Grade] and save it in temp
var temp = grades[item.Grade];
if( ! temp ) {
    // It was missing, so use an empty object as the default value
    temp = {};
}
// Now save the result in grades[item.Grade] (in case it was missing)
// and in grade
grades[item.Grade] = temp;
var grade = temp;

您可能会注意到,在grades[item.Grade]已存在的情况下,我们将刚刚获取的值存回相同的属性中。当然,这是不必要的,如果您像这样编写代码,您可能不会这样做。相反,您可以简化它:
var grade = grades[item.Grade];
if( ! grade ) {
    grade = grades[item.Grade] = {};
}

这是一种完全合理的编写代码的方式,而且效率更高。它还提供了一种比使用“||”习语所依赖的 "真实性" 测试更具体的测试方法。例如,您可能想使用 if( grade === undefined ) 而不是 if( ! grade )


这非常接近我所需的内容。您如何修改它,以使结果成为形式为[grade,domain,domain,grade,domain,domain,grade,domain]的简单数组? - dchess
你在这里询问的数组格式听起来根本不实用。当成绩和领域混合在一起时,如何区分它们?这将非常难以处理。可以轻松地将此“grades”对象转换为所需的任何格式,并直接使用数据调用任何类型的函数。但不要被像那个数组这样行不通的数据格式所困扰。真正需要问的问题是你想从中获取什么数据。 - Michael Geary
但是,如果您想要原始问题中所提到的输出格式,那很简单。我更新了答案,并添加了几行代码以生成该格式。 - Michael Geary
我希望生成一些HTML,其中包含年级作为标题,该年级的唯一领域在其下方作为无序列表。我想数组可以让我在其中插入一些HTML标记并创建HTML字符串。像这样["<div>Grade1<li>Domain1</li><li>Domain2</li></div><div>Grade2<li>Domain1</li>等等..."] - dchess
1
@Rai 当然没问题,我在答案末尾加了一条注释来解释它。 - Michael Geary
显示剩余2条评论

13

我需要对一些JSON对象进行去重,因此我偶然发现了这个页面。不过,我使用了简短的ES6解决方案(无需外部库),并在Chrome Dev Tools Snippets中运行了它:

const data = [ /* any list of objects */ ];
const set = new Set(data.map(item => JSON.stringify(item)));
const dedup = [...set].map(item => JSON.parse(item));
console.log(`Removed ${data.length - dedup.length} elements`);
console.log(dedup);

8

重新提出一个旧问题,但我想发表对@adeneo答案的迭代。那个答案完全通用,但对于这种用例,它可能更有效率(在我的机器上有几千个对象的数组中速度很慢)。如果您知道需要比较的对象的特定属性,请直接进行比较:

var sl = standardsList;
var out = [];

for (var i = 0, l = sl.length; i < l; i++) {
    var unique = true;
    for (var j = 0, k = out.length; j < k; j++) {
        if ((sl[i].Grade === out[j].Grade) && (sl[i].Domain === out[j].Domain)) {
            unique = false;
        }
    }
    if (unique) {
        out.push(sl[i]);
    }
}

console.log(sl.length); // 10
console.log(out.length); // 5

最佳答案在这里,我个人认为。 - JP Lew

6
您的情况的Javascript解决方案:
console.log(unique(standardsList));

function unique(obj){
    var uniques=[];
    var stringify={};
    for(var i=0;i<obj.length;i++){
       var keys=Object.keys(obj[i]);
       keys.sort(function(a,b) {return a-b});
       var str='';
        for(var j=0;j<keys.length;j++){
           str+= JSON.stringify(keys[j]);
           str+= JSON.stringify(obj[i][keys[j]]);
        }
        if(!stringify.hasOwnProperty(str)){
            uniques.push(obj[i]);
            stringify[str]=true;
        }
    }
    return uniques;
}

5

以下方法可以实现你想要的功能。它根据所有属性值过滤数组。

    var standardsList = [
  { "Grade": "Math K", "Domain": "Counting & Cardinality" },
  { "Grade": "Math K", "Domain": "Counting & Cardinality" },
  { "Grade": "Math K", "Domain": "Counting & Cardinality" },
  { "Grade": "Math K", "Domain": "Counting & Cardinality" },
  { "Grade": "Math K", "Domain": "Geometry" },
  { "Grade": "Math 1", "Domain": "Counting & Cardinality" },
  { "Grade": "Math 1", "Domain": "Counting & Cardinality" },
  { "Grade": "Math 1", "Domain": "Orders of Operation" },
  { "Grade": "Math 2", "Domain": "Geometry" },
  { "Grade": "Math 2", "Domain": "Geometry" }
];

const removeDupliactes = (values) => {
  let concatArray = values.map(eachValue => {
    return Object.values(eachValue).join('')
  })
  let filterValues = values.filter((value, index) => {
    return concatArray.indexOf(concatArray[index]) === index

  })
  return filterValues
}
removeDupliactes(standardsList) 

这是结果

[{Grade: "Math K", Domain: "Counting & Cardinality"}

{Grade: "Math K", Domain: "Geometry"}

{Grade: "Math 1", Domain: "Counting & Cardinality"}

{Grade: "Math 1", Domain: "Orders of Operation"}

{Grade: "Math 2", Domain: "Geometry"}] 

5

var standardsList = [
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Geometry"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Orders of Operation"},
    {"Grade": "Math 2", "Domain": "Geometry"},
    {"Grade": "Math 2", "Domain": "Geometry"}
];

standardsList = standardsList.filter((li, idx, self) => self.map(itm => itm.Grade+itm.Domain).indexOf(li.Grade+li.Domain) === idx)

document.write(JSON.stringify(standardsList))

这里有一种功能性的方法可以更加轻松地完成它。
standardsList = standardsList.filter((li, idx, self) => self.map(itm => iem.Grade+itm.domain).indexOf(li.Grade+li.domain) === idx)

1
standardsList = standardsList.filter((li, idx, self) => self.map(itm => itm.Grade+itm.domain).indexOf(li.Grade+li.domain) === idx)标准列表=标准列表.filter((li,idx,self)=>self.map(itm=>itm.Grade+itm.domain).indexOf(li.Grade+li.domain)===idx) - khalid MZIBRA

4
使用 Map 去除重复项。(对于新读者)

var standardsList = [
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Counting & Cardinality"},
    {"Grade": "Math K", "Domain": "Geometry"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Counting & Cardinality"},
    {"Grade": "Math 1", "Domain": "Orders of Operation"},
    {"Grade": "Math 2", "Domain": "Geometry"},
    {"Grade": "Math 2", "Domain": "Geometry"}
];

var grades = new Map();
standardsList.forEach( function( item ) {
    grades.set(JSON.stringify(item), item);
});

console.log( [...grades.values()]);

/*
[
  { Grade: 'Math K', Domain: 'Counting & Cardinality' },
  { Grade: 'Math K', Domain: 'Geometry' },
  { Grade: 'Math 1', Domain: 'Counting & Cardinality' },
  { Grade: 'Math 1', Domain: 'Orders of Operation' },
  { Grade: 'Math 2', Domain: 'Geometry' }
]
*/


这太棒了,我必须在它运行后将这些结果推回到一个新数组中,但它是完美的。 - Andy

3
以下是我所使用的方法:
_.uniq(standardsList, JSON.stringify)

这对于非常长的列表可能会比较慢。

4
将对象转换为字符串并不能保证对象是相同的。键和值如果顺序不一致就会导致不匹配。 - adeneo
你可以对每个单独的项目进行排序,以给它们一个标准格式,以解决 @adeneo 提到的问题。然后,您可以将项目字符串化,运行 _.uniq,然后取消字符串化。 - Leo
1
@Leo - 你不能对一个对象进行排序。 - adeneo
1
如果你不是字面意思,那么将它们转换为字符串有什么帮助呢? - cookie monster
1
但是对于嵌套对象而言,这并不适用,需要编写一些相当复杂的代码。使用 _.isEqual 可能会有一些好的解决方案。 - stubailo
显示剩余4条评论

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