Javascript - Find (not remove) elements with duplicate properties in an object array - javascript

I want to fetch an object array, check if multiple properties are duplicate at the same time. Finally, the duplicate elements are meant to be alerted. i.e. for the array:
[
{
"language" : "english",
"type" : "a",
"value" : "value1"
},
{
"language" : "english",
"type" : "a",
"value" : "value2"
},
{
"language" : "english",
"type" : "b",
"value" : "value3"
},
{
"language" : "spanish",
"type" : "a",
"value" : "valor1"
}
];
The alert should be: "Too many elements with language english and type a".
I sought for it, yet the answers were either dirty map/reduce implementations that made VisualStudio2015 pissed off or finding a full duplicates (not only based on specific properties)

Performance wise, I think the best option is to iterate on each element of the list while updating a counter of the encountered keys.
You want to alert when counter == 2. You should not test counter > 1 because it would repeat the same alert several times, when you have more than 2 pairs with the same language and same type.
var list = [{
"language" : "english",
"type" : "a",
"value" : "value1"
},{
"language" : "english",
"type" : "a",
"value" : "value2"
},{
"language" : "english",
"type" : "a",
"value" : "value3"
},{
"language" : "english",
"type" : "b",
"value" : "value3"
},{
"language" : "spanish",
"type" : "a",
"value" : "valor1"
},{
"language" : "spanish",
"type" : "a",
"value" : "valor2"
}];
var counter = {};
list.forEach(function(o) {
var key = o.language + '|' + o.type;
if((counter[key] = (counter[key] || 0) + 1) == 2) {
console.log('Too many elements with language '+o.language+' and type '+o.type);
}
});

Related

array in array filter - javascript

hello i need help with array , as you can see my data
{
"age" : "18",
"altKategoriler" : [ "Dramalar" ],
"category" : [ "Aksiyon", "Heyecanlı", "Gerilim" ],
"id" : 5240718100,
"img" : "https://i.ibb.co/k8wx5C8/AAAABW9-ZJQOg-MRljz-Zwe30-JZw-Hf4vq-ERHq6-HMva5-ODHln-Ci-OEV6ir-Rcjt88tcnm-QGQCKpr-K9h-Oll-Ln-Sbb-EI.jpg",
"izlenilmeSayisi" : 0,
"logo" : "https://i.ibb.co/Rb2SrcB/AAAABfcrhh-Rni-Ok-Ct2l-Rys-ZYk-Oi-T0-XTeagkrw-Mkm-U0h-Lr-WIQZHEHg-VXihf-OWCwz-Vv-Qd7u-Ffn-DFZEX2-Ob.webp",
"oyuncuKadrosu" : [ "Diego Luna", "Michael Pena", "Scoot McNairy", "Tenoch Huerta", "Joaquin Cosio" ],
"senarist" : [ "Doug Miro" ],
"time" : "3 Sezon",
"title" : "Narcos: Mexico",
"type" : "Dizi",
"videoDescription" : "Guadalajara Karteli'nin yükselişinin gerçek öyküsünü anlatan bu yeni ve cesur Narcos hikâyesinde, Meksika'daki uyuşturucu savaşının 1980'lerdeki doğuşuna tanıklık edin.",
"videoQuality" : "HD",
"videosrc" : "https://tr.vid.web.acsta.net/uk/medias/nmedia/90/18/10/18/19/19550785_hd_013.mp4",
"year" : "2021",
"yonetmen" : [ "Carlo Bernard", "Chris Brancato" ]
}
I can access elements such as id , title or logo because they are not arrays.
How can I loop through the data inside the array since there is an array in the category in yield?
var data = this.database.filter((item) => item.type == searchType)
var data = this.database.filter((item) => item.category == searchCategory)
It's okay because my type value doesn't have an array.
But when I enter my category value, it only gets the first index[0]. It does not look at other indexes.
in summary,
item.category[0] , item.category[1] , item.category[2]...........
How can I get index browsing like
if your data looks like this :
let data ={
"age" : "18",
"altKategoriler" : [ "Dramalar" ],
"category" : [ "Aksiyon", "Heyecanlı", "Gerilim" ],
"id" : 5240718100,
"img" : "https://i.ibb.co/k8wx5C8/AAAABW9-ZJQOg-MRljz-Zwe30-JZw-Hf4vq-ERHq6-HMva5-ODHln-Ci-OEV6ir-Rcjt88tcnm-QGQCKpr-K9h-Oll-Ln-Sbb-EI.jpg",
"izlenilmeSayisi" : 0,
"logo" : "https://i.ibb.co/Rb2SrcB/AAAABfcrhh-Rni-Ok-Ct2l-Rys-ZYk-Oi-T0-XTeagkrw-Mkm-U0h-Lr-WIQZHEHg-VXihf-OWCwz-Vv-Qd7u-Ffn-DFZEX2-Ob.webp",
"oyuncuKadrosu" : [ "Diego Luna", "Michael Pena", "Scoot McNairy", "Tenoch Huerta", "Joaquin Cosio" ],
"senarist" : [ "Doug Miro" ],
"time" : "3 Sezon",
"title" : "Narcos: Mexico",
"type" : "Dizi",
"videoDescription" : "Guadalajara Karteli'nin yükselişinin gerçek öyküsünü anlatan bu yeni ve cesur Narcos hikâyesinde, Meksika'daki uyuşturucu savaşının 1980'lerdeki doğuşuna tanıklık edin.",
"videoQuality" : "HD",
"videosrc" : "https://tr.vid.web.acsta.net/uk/medias/nmedia/90/18/10/18/19/19550785_hd_013.mp4",
"year" : "2021",
"yonetmen" : [ "Carlo Bernard", "Chris Brancato" ]
}
and if we have array of data you can do something like this :
myArray.filter(item=>item.category.indexOf(searchCategory)>=0)
but if you want to explore in object rather than array you can do this :
data.category.indexOf(searchCategory)>=0
You could make this a bit generic, by testing whether the targeted field is an array, using Array.isArray, and then call a filter on each element, and see if any is positive (using .some()). The filter can be function that is provided, so that it can perform a simple match, or apply a regular expression, or anything else.
Instead of testing with Array.isArray you could skip that step and check whether the value has a .some() method. If so, calling it will give the desired outcome, and otherwise (using the .? and ?? operators), the filter should be applied to the value as a whole:
Here is how that looks:
function applyFilter(data, field, filter) {
return data.filter(item => item[field]?.some(filter) ?? filter(item));
}
// Example use:
var data = [{
"category" : [ "Action", "Thriller", "Horror"],
"type" : "Series",
}, {
"category" : [ "Historical", "Romance" ],
"type" : "Theatre",
}];
// Find entries that have a category that looks like "roman*":
var result = applyFilter(data, "category", value => /^roman.*/i.test(value));
console.log(result);
If you are running on an older version of JavaScript, and don't have support for .? or ??, then use:
return data.filter(item => Array.isArray(item[field])
? item[field].some(filter)
: filter(item));

"Evaluate" Function that is stored in MongoDB Server

My collection looks like this:
> db.projects_columns.find()
{ "_id" : "5b28866a13311e44a82e4b8d", "checkbox" : true }
{ "_id" : "5b28866a13311e44a82e4b8e", "field" : "_id", "title" : "ID", "sortable" : true }
{ "_id" : "5b28866a13311e44a82e4b8f", "field" : "Project", "title" : "Project", "editable" : { "title" : "Project", "placeholder" : "Project" } }
{ "_id" : "5b28866a13311e44a82e4b90", "field" : "Owner", "title" : "Owner", "editable" : { "title" : "Owner", "placeholder" : "Owner" } }
{ "_id" : "5b28866a13311e44a82e4b91", "field" : "Name #1", "title" : "Name #1", "editable" : { "title" : "Name #1", "placeholder" : "Name #1" } }
{ "_id" : "5b28866a13311e44a82e4b92", "field" : "Name #2", "title" : "Name #2", "editable" : { "title" : "Name #2", "placeholder" : "Name #2" } }
{ "_id" : "5b28866a13311e44a82e4b93", "field" : "Status", "title" : "Status", "editable" : { "title" : "Status", "type" : "select", "source" : [ { "value" : "", "text" : "Not Selected" }, { "value" : "Not Started", "text" : "Not Started" }, { "value" : "WIP", "text" : "WIP" }, { "value" : "Completed", "text" : "Completed" } ], "display" : "function (value, sourceData) { var colors = { 0: 'Gray', 1: '#E67C73', 2: '#F6B86B', 3: '#57BB8A' }; var status_ele = $.grep(sourceData, function(ele){ return ele.value == value; }); $(this).text(status_ele[0].text).css('color', colors[value]); }", "showbuttons" : false } }
You can see that in the very last document that I have stored a function as text.Now the idea is that I will request this data and will be in an Javascript Array format.
But I want to be able to have my function without the quotes! You can see that simply evaluating it will not work because I need to have it still needs to be inside of the object ready to be executed when the array is used.
How can I achieve this?
Thanks for any help!
There are two possible solutions, but neither particularly safe and you should strongly consider why you need to store functions as strings in the first place. That being said, you could do two things.
The simplest is to use eval. To do so, you would have to first parse the object like normal, and then set the property that you want to the result of eval-ing the function string, like so:
// Pass in whatever JSON you want to parse
var myObject = JSON.parse(myJSONString);
// Converts the string to a function
myObject.display = eval("(" + myObject.display + ")");
// Call the function with whatever parameters you want
myObject.display(param1, param2);
The additional parentheses are to make sure that evaluation works correctly. Note, that this is not considered safe by Mozilla and there is an explicit recommendation not to use eval.
The second option is to use the Function constructor. To do so, you would need to restructure your data so that you store the parameters separately, so you could do something like this:
var myObject = JSON.parse(myJSONString);
// displayParam1 and displayParam2 are the stored names of your parameters for the function
myObject.display = Function(myObject.displayParam1, myObject.displayParam2, myObject.display)
This method definitely takes more modification, so if you want to use your existing structure, I recommend eval. However, again, make sure that this is absolutely necessary because both are considered unsafe since outside actors could basically inject code into your server.

Query to Match on nth Document of an Array

I am new to MongoDB and I am doing some exercises on it. In particular I got stuck on this exercise, of which I report here the question:
Given the following structure for document "Restaurant":
{
"_id" : ObjectId("5704adbc2eb7ebe23f582818"),
"address" : {
"building" : "1007",
"coord" : [
-73.856077,
40.848447
],
"street" : "Morris Park Ave",
"zipcode" : "10462"
},
"borough" : "Bronx",
"cuisine" : "Bakery",
"grades" : [
{
"date" : ISODate("2014-03-03T00:00:00Z"),
"grade" : "A",
"score" : 2
},
{
"date" : ISODate("2013-09-11T00:00:00Z"),
"grade" : "A",
"score" : 6
},
{
"date" : ISODate("2013-01-24T00:00:00Z"),
"grade" : "A",
"score" : 10
},
{
"date" : ISODate("2011-11-23T00:00:00Z"),
"grade" : "A",
"score" : 9
},
{
"date" : ISODate("2011-03-10T00:00:00Z"),
"grade" : "B",
"score" : 14
}
],
"name" : "Morris Park Bake Shop",
"restaurant_id" : "30075445"
}
Write a MongoDB query to find the restaurant Id, name and grades for those restaurants where 2nd element of grades array contains a grade of "A" and score 9 on an ISODate "2014-08-11T00:00:00Z".
I wrote this query:
db.restaurants.find(
{
'grades.1': {
'score': 'A',
'grade': 9,
'date' : ISODate("2014-08-11T00:00:00Z")
}
},
{
restaurant_id: 1,
name: 1,
grades: 1
});
which is not working.
The solution provided is the following:
db.restaurants.find(
{ "grades.1.date": ISODate("2014-08-11T00:00:00Z"),
"grades.1.grade":"A" ,
"grades.1.score" : 9
},
{"restaurant_id" : 1,"name":1,"grades":1}
);
My questions are:
is there a way to write the query avoiding to repeat the grades.1 part?
Why is my query wrong, given that grades.1 is a document object?
If it can help answering my question, I am using MongoDB shell version: 3.2.4
EDIT:
I found an answer to question 2 thanks to this question.
In particular I discovered that order matters. Indeed, if I perform the following query, I get a valid result:
db.restaurants.find({'grades.1': {'date': ISODate("2014-08-11T00:00:00Z"), 'grade':'A', score:9}}, {restaurant_id:1, name:1, grades:1})
Note that this query works only because all subdocument's "fields" are specified, and they are specified in the same order.
Not really. But perhaps an explanation of what you "can" do:
db.junk.find({
"grades": {
"$elemMatch": {
"date" : ISODate("2014-03-03T00:00:00Z"),
"grade" : "A",
"score" : 2
}
},
"$where": function() {
var grade = this.grades[0];
return (
grade.date.valueOf() == ISODate("2014-03-03T00:00:00Z").valueOf() &&
grade.grade === "A" &&
grade.score ==== 2
)
}
})
The $elemMatch allows you to shorten a little, but it is not the "nth" element of the array. In order to narrow that further you need to use the $where clause to inspect the "nth" array element to see if all values are a match.
db.junk.aggregate([
{ "$match": {
"grades": {
"$elemMatch": {
"date" : ISODate("2014-03-03T00:00:00Z"),
"grade" : "A",
"score" : 2
}
}
}},
{ "$redact": {
"$cond": {
"if": {
"$let": {
"vars": { "grade": { "$arrayElemAt": [ "$grades", 0 ] } },
"in": {
"$and": [
{ "$eq": [ "$grade.date", ISODate("2014-03-03T00:00:00Z") ] },
{ "$eq": [ "$grade.grade", "A" ] },
{ "$eq": [ "$grade.score", 2 ] }
]
}
}
},
"then": "$$KEEP",
"else": "$$PRUNE"
}
}}
])
You can do the same logic with $redact as well using .aggregate(). It runs a little quicker, but the basic truth should be clear by now.
So using "dot notation" to specify the "nth" position for each element within the array like you have already done is the most efficient and "brief" way to write this. You cannot make it shorter or better.
Your other attempt is looking for a "document" within "grades.1" that matches exactly the document condition you are providing. If for any reason those are not the only fields present, or if they are indeed in "different order" in the stored document, then such a query condition will not be a match.

MongoDB return specific fields from array

I want to limit my query's result to a set of fields. This is one of my documents:
{
"_id" : "WA9QRuiWtGsr4amtT",
"status" : 3,
"data" : [
{
"name" : "0",
"value" : "Text ..."
},
{
"name" : "1",
"value" : "12345678"
},
{
"name" : "2",
"value" : "Text"
},
{
"name" : "4",
"value" : "2"
},
{
"name" : "8",
"value" : true
},
{
"name" : "26",
"value" : true
},
],
"userId" : "7ouEumtudgC2HX4fF",
"updatedAt" : NumberLong(1415903962863)
}
I want to limit the output to the status field as well a the first and third data document.
This is what I tried:
Meteor.publish('cases', function () {
var fields = {
currentStatus: 1,
'data.0': 1,
'data.2': 1
};
return Cases.find({}, { fields: fields });
});
Sadly it doesn't work. Something else I found is $elemMatch but it only returns the first element:
data: {
$elemMatch: {
name: {
$in: ['0', '2']
}
}
},
How can I limit the output to these fields?
To display status and data(unlimited) fields try
cases.find({}, {"status":1, "data":1})
This is simple query, to limit "data" output you will need to work harder :)
Get 1 element by data.name (not by position):
cases.find({}, {status:1, "data": {$elemMatch:{name:"0"}}})
Get 1 element by data.name, but from a list of values:
cases.find({}, {status:1, "data": {$elemMatch:{name:{$in:["0", "1"]}}}})
To get close to your question, you may try redact. That is new in Mongodb 2.6.
Or play with $unwind and .aggregate() in previous editions.
So far, I do not see a way to return array elements based on a position.

java.lang.ClassCastException calling RedQueryBuilderFactory.create with args

This line in my JS file:
RedQueryBuilderFactory.create(config,
'SELECT "x0"."title", "x0"."priority" FROM "ticket" "x0" WHERE ("x0"."status" = (?))',
[]
);
works fine witih an empty array as the 3rd parameter. This parameter is supposed to be an array of strings according to the documentation and any sample code I can find. When I pass a string in the array it fails:
RedQueryBuilderFactory.create(config,
'SELECT "x0"."title", "x0"."priority" FROM "ticket" "x0" WHERE ("x0"."status" = (?))',
['in_process']
);
I get java.lang.ClassCastException in the Safari console. Here's the related part of the config if it's relevant:
var config = {
meta : {
tables : [ {
"name" : "ticket",
"label" : "Ticket",
"columns" : [ {
"name" : "title",
"label" : "Title",
"type" : "STRING",
"size" : 255
}, {
"name" : "priority",
"label" : "Priority",
"type" : "REF"
} ],
fks : []
} ],
types : [ {
"name" : "REF",
"editor" : "SELECT",
"operators" : [ {
"name" : "IN",
"label" : "any of",
"cardinality" : "MULTI"
}]
} ]
}
};
Looks like this is a bug in passing in parameter values. Internally it is expecting a collection but this is not happening.
Best if you raise a https://github.com/salk31/RedQueryBuilder bug report here?
NB Should be "IN" not "="

Categories

Resources