Get array of all unique object values based on property name - javascript

How can I get an array with all the unique values based on a property name?
In my case my object looks like this and I want an array with the unique documentID's.
const file = {
invoice: {
invoiceID: 1,
documentID: 5
},
reminders: [
{
reminderID: 1,
documentID: 1
},
{
reminderID: 2,
documentID: 1
}
]
}
The result should be an array [5, 1] //The unique documentID's are 5 and 1
It doesn't seem like possible to add a property name to the Object.values() function.

You can use Set to get unique documentID.
const file = {
invoice: {
invoiceID: 1,
documentID: 5
},
reminders: [
{
reminderID: 1,
documentID: 1
},
{
reminderID: 2,
documentID: 1
}
],
payments: {
documentID : 5
}
};
var keys = Object.keys(file).map(key=>file[key].map ? file[key].map(i=>i.documentID) : file[key].documentID)
var keysFlattened= [].concat.apply([], keys);
var unique = new Set(keysFlattened);
console.log(Array.from(unique));

I use something like this that does what you want I think
const keepUniqueBy = key => (array, item) => {
if (array.find(i => item[key] === i[key])) {
return array;
} else {
return [ ...array, item ];
}
};
Then you can simply: const unique = reminders.reduce(keepUniqueBy('documentID'))
NB: It's probably low performing, but for small arrays it doesn't matter.

Related

How can I push data to an array with fixed length in js

I have an array of object something like this.
[
{
channelName: "WhatsApp"
count: 1
date: "2021-06-05"
},{
channelName: "RCS"
count: 1
date: "2021-06-09"
}
]
There are two types of channel names 1. WhatsApp and 2nd are RCS. I want to filter out count with specific channel names and store it in a separate array. But the problem here is I want both the array length should be the same. If there is data for WhatsApp then it will add the count otherwise it will add 0 in place of it.
For that, I did something like this but this does not work .
const filterData = (data: any) => {
const category: any = [];
const whatsAppCount: any = [];
const rcsCount: any = [];
data.filter((item: any, i: number) => {
if (item.channelName === "WhatsApp") {
whatsAppCount[i] = item.count;
} else if (item.channelName === "RCS") {
rcsCount[i] = item.count;
}
category.push(item.date);
});
setGraphData({
category: category,
whatsApp: whatsAppCount,
rcs: rcsCount,
});
console.log("handleRun", { category, whatsAppCount, rcsCount });
};
Here the console log gives something like this.
whatsAppCount: [1, 2, 13, 21, empty × 2, 8, 5, empty, 18, empty, 12, 4]
rcsCount: [empty × 4, 1, 12, empty × 2, 1, empty, 8]
Here in the place of empty, I want 0. I am not sure how to do that any help would be great.
When you create the arrays, but before populating them, there are two functions that can help with initialization:
// create an array with 10 slots preallocated but empty (not `undefined`)
let arr = new Array(10);
// set all allocated slots to a value (`0` in this case)
arr = arr.fill(0);
Since you know the lengths you want ahead of time, you can use that to pre-size the arrays on construction. Then use .fill to initialize the values to 0. Once, that's done, you can continue with your counting and updating the arrays.
Reference:
Array constructor
Array.prototype.fill()
I would suggest you use the map-function, mapping the unwanted values to undefined, letting the other values "pass through" (unmodified), eg.:
const filtered = data.map((each) => {
if (wantToKeep) {
return each;
} else {
return undefined;
}
});
Note, this is not the exact solution - but a general idea.
You can use forEach and push(0) for the empty records.
const data = [
{
channelName: "WhatsApp",
count: 1,
date: "2021-06-05",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-01",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-06",
},
{
channelName: "WhatsApp",
count: 5,
date: "2021-06-11",
},
{
channelName: "WhatsApp",
count: 7,
date: "2021-06-23",
},
{
channelName: "RCS",
count: 1,
date: "2021-06-09",
},
];
const category = [];
const whatsAppCount = [];
const rcsCount = [];
data.forEach(x => {
if (x.channelName === "WhatsApp") {
whatsAppCount.push(x.count);
rcsCount.push(0);
} else if (x.channelName === "RCS") {
whatsAppCount.push(0);
rcsCount.push(x.count);
}
category.push(x.date);
});
console.log({ whatsAppCount });
console.log({ rcsCount });
console.log({ category });

Inside an Object.value loop how can I get the key

I have to create match condition based on an array my array will look like below
var groupData={
A:[
{rollnum: 1, name:'Arya', age:15},
{rollnum: 2, name:'Aryan', age:15}
],
B:[
{rollnum:11, name:'Biba', age:15},
{rollnum:12, name:'Bimisha', age:15}
]
}
I am looping using for loop. How can reduce the loops. Can any one suggest me a proper way for this
Object.values(groupData).flat().forEach((rowitem)=>{
query={};
Object.keys(rowitem).forEach(eachField=>{
query[eachField]["$in"].push(rowitem[eachField])
});
fullarray[Object.keys(groupData)]=matchQuery;
})
I need an output (fullarray) like below
{
'A':{
rollnum:{'$in':[1,2]},
name: {'$in':['Arya', 'Aryan']},
age: {'$in':[15]}
},
'B':{
rollnum:{'$in':[11,12]},
name: {'$in':['Biba', 'Bimisha']},
age: {'$in':[15]}
}
}
Here 'A' 'B' is not coming correctly
Don't use Object.values() since that discards the A and B keys.
Use nested loops, one loop for the properties in the object, and a nested loop for the arrays.
You need to create the nested objects and arrays before you can add to them.
var groupData = { A:
[ { rollnum: 1,
name: 'Arya',
age:15},
{ rollnum: 2,
name: 'Aryan',
age:15}, ],
B:
[ { rollnum: 11,
name: 'Biba',
age:15},
{ rollnum: 12,
name: 'Bimisha',
age:15} ] }
result = {};
Object.entries(groupData).forEach(([key, arr]) => {
if (!result[key]) {
result[key] = {};
}
cur = result[key];
arr.forEach(obj => {
Object.entries(obj).forEach(([key2, val]) => {
if (!cur[key2]) {
cur[key2] = {
"$in": []
};
}
cur[key2]["$in"].push(val);
});
});
});
console.log(result);

Set new Sequence in Object from Array after deletion of specific object

My Goal:
I need to have a continuous sequence of numbers in the sequenceIndex which is a value in my object.
So when I remove a specific object the sequence index of the other objects is of course not continuous anymore. The object I remove is being checked against a specific value to see whether there are other objects in the array which share the same value (second if-statement). If so then there should be a new value set which is continuous.
The output is that the iterator in the if-statement is always the same for all objects manipulated.
From this:
const objectsArray = [
{
folder: "folderName",
documents: [
{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 1,
sequenceIndex: "1",
documentType: "letter"
},
{
id: 2,
sequenceIndex: "2",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "3",
documentType: "letter"
}
]
}
];
By removing id 1 and 2 I would like to come to this (see continuous sequenceIndex):
const desiredObjectsArray = [
{
folder: "folderName",
documents: [
{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "1",
documentType: "letter"
}
]
}
];
My code so far:
case ActionType.RemoveDocumentInSpecificFolder:
return state.map(file => {
// if in the correct folder remove the object with the delivered id
if (file.folder=== folder) {
remove(file.documents, {
id: action.payload.documents[0].id
});
// create newObjArray from objects which share a specific value and replace the sequence index by new value
const newObjArray = file.documents.map((obj: any) => {
// if the object has the specific value create new object with new sequenceIndex
if (obj.documentType === action.payload.documents[0].documentType) {
//poor attempt to create a sequence
let i = 0;
const correctedSequenceDocObject = { ...obj, sequenceIndex: i };
i++;
return correctedSequenceDocObject;
}
return {
...obj
};
});
return {
...file,
documents: newObjArray
};
}
return file;
});
I hope someone can guide me in the right direction. I would also always appreciate a suggestion of best practice :)
Best regards
You can use filter and map something like this
const arr = [{folder: "folderName",documents: [{id: 0,sequenceIndex: "0",documentType: "letter"},{id: 1,sequenceIndex: "1",documentType: "letter"},{id: 2,sequenceIndex: "2",documentType: "letter"},{id: 3,sequenceIndex: "3",documentType: "letter"}]}];
let getInSequence = (filterId) => {
return arr[0].documents.filter(({ id }) => !filterId.includes(id))
.map((v, i) => ({ ...v, sequenceIndex: i }))
}
console.log(getInSequence([1, 2]))
As commented:
This is the classic case where .filter().map() will be useful. filter the data and then use .map((o, i) => ({ ...obj, sequenceIndex: i+1 }) )
Following is the sample:
const objectsArray = [{
folder: "folderName",
documents: [{
id: 0,
sequenceIndex: "0",
documentType: "letter"
},
{
id: 1,
sequenceIndex: "1",
documentType: "letter"
},
{
id: 2,
sequenceIndex: "2",
documentType: "letter"
},
{
id: 3,
sequenceIndex: "3",
documentType: "letter"
}
]
}];
const ignoreIds = [1, 2]
const updatedDocs = objectsArray[0].documents
.filter(({
id
}) => !ignoreIds.includes(id))
.map((doc, index) => ({ ...doc,
sequenceIndex: index
}));
console.log(updatedDocs)
Now lets cover your attempt
const newObjArray = file.documents.map((obj: any) => {
// For all the unmatching objects, you will have undefined as object as you are using `.map`
// This will make you `newObjArray: Array<IDocument | undefined>` which can break your code.
if (obj.documentType === action.payload.documents[0].documentType) {
// This will set it as 0 in every iteration making i as 0 always.
let i = 0;
const correctedSequenceDocObject = { ...obj, sequenceIndex: i };
i++;
return correctedSequenceDocObject;
}
return { ...obj };
});
An alternate with single loop:
Idea:
Create a loop using Array.reduce and pass it a blank array as list.
Add a check and inside it, push value to this list.
For sequenceIndex, fetch last element and fetch its sequenceIndex. Add one and set it again.
const newObjArray = file.documents.reduce((acc: Array<IDocument>, obj: any) => {
if (obj.documentType === action.payload.documents[0].documentType) {
const sequenceIndex: number = (!!acc[acc.length - 1] ? acc[acc.length - 1].sequenceIndex : 1) + 1;
acc.push({ ...obj, sequenceIndex });
}
return acc;
});
The solution I used now to this problem was:
let count = 0;
const newObject = file.documents.map(obj => {
if (obj.documentType === firstDocument.documentType) {
count++;
return { ...obj, sequenceIndex: count - 1 };
}
return obj;
});
Both of the provided answers were not able to handle objects which were out of interest because of the different documentType so they dropped the object. with this solution, I am checking against the last element and increasing the count if the last element was the same documentType.

Compare 2 arrays and assign matching value [duplicate]

This question already has answers here:
Merge property from an array of objects into another based on property value lodash
(5 answers)
Closed 4 years ago.
I have 2 array of objects
The first one called data:
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
]
and the second called subs:
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
]
In which I want to compare that if they have the same ID, the subs array will pass its name value to it and if it does not match that it puts a '-' in the data array, try this way:
data.forEach((d)=>{
subs.forEach((s)=>{
if(d.id === s.id){
d.subname = s.name;
}
else {
d.subname = '-';
}
});
});
But always assign the values with '-' as if it does not match any. What part am I doing wrong? Is there any other simpler way to do this? I would greatly appreciate your help.
The size of the subs array may vary.
It looks like you are not exiting the inner loop when a successful match is found.
In the first example where you are looking for a match for Piero, in your first iteration 1===1 and d.subname is correctly set to 'Temprano'. However, you then continue to compare the values- 1 !== 4 so Temprano is overwritten with '-', and 1 !== 7 so it is overwritten again.
An alternate approach:
data.forEach(d => {
const match = subs.find(s => s.id === d.id);
d.subname = match ? match.name : '-';});
I'd also recommend adding a case where you're not expecting to find a match, so you can see that it works in both cases!
https://codepen.io/anon/pen/MGGBLP?editors=0010
const data = [
{
id: 1,
nombre: 'Piero',
},
{
id: 4,
nombre: 'Nelson',
},
{
id: 7,
nombre: 'Diego'
},
];
const subs = [
{
id: 1,
name: 'Temprano',
},
{
id: 4,
name: 'A tiempo',
},
{
id: 7,
name: 'Tarde'
},
];
// by caching one of the arrays in an object, it reduces the run time to linear.
const obj = subs.reduce((acc, item) => {
acc[item.id] = item;
return acc;
})
data.forEach(d => {
if (d.id in obj) {
d.subname = obj[d.id].name;
} else {
d.subname = '-';
}
});
console.log(data);
You just need two lines for this:
var findIds = id => subs.find(findId => findId.id === id);
data.forEach(findId => Object.assign(findId, findIds(findId.id)));
Your data array object should now include the name property from it's respective id sharing object in subs array.
jsFiddle: https://jsfiddle.net/AndrewL64/9k1d3oj2/1/

How to add non duplicate objects in an array in javascript?

I want to add non-duplicate objects into a new array.
var array = [
{
id: 1,
label: 'one'
},
{
id: 1,
label: 'one'
},
{
id: 2,
label: 'two'
}
];
var uniqueProducts = array.filter(function(elem, i, array) {
return array.indexOf(elem) === i;
});
console.log('uniqueProducts', uniqueProducts);
// output: [object, object, object]
live code
I like the class based approach using es6. The example uses lodash's _.isEqual method to determine equality of objects.
var array = [{
id: 1,
label: 'one'
}, {
id: 1,
label: 'one'
}, {
id: 2,
label: 'two'
}];
class UniqueArray extends Array {
constructor(array) {
super();
array.forEach(a => {
if (! this.find(v => _.isEqual(v, a))) this.push(a);
});
}
}
var unique = new UniqueArray(array);
console.log(unique);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.4/lodash.min.js"></script>
Usually, you use an object to keep track of your unique keys. Then, you convert the object to an array of all property values.
It's best to include a unique id-like property that you can use as an identifier. If you don't have one, you need to generate it yourself using JSON.stringify or a custom method. Stringifying your object will have a downside: the order of the keys does not have to be consistent.
You could create an objectsAreEqual method with support for deep comparison, but this will slow your function down immensely.
In two steps:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
// Create a string representation of your object
function getHash(obj) {
return Object.keys(obj)
.sort() // Keys don't have to be sorted, do it manually here
.map(function(k) {
return k + "_" + obj[k]; // Prefix key name so {a: 1} != {b: 1}
})
.join("_"); // separate key-value-pairs by a _
}
function getHashBetterSolution(obj) {
return obj.id; // Include unique ID in object and use that
};
// When using `getHashBetterSolution`:
// { '1': { id: '1', label: 'one' }, '2': /*etc.*/ }
var uniquesObj = array.reduce(function(res, cur) {
res[getHash(cur)] = cur;
return res;
}, {});
// Convert back to array by looping over all keys
var uniquesArr = Object.keys(uniquesObj).map(function(k) {
return uniquesObj[k];
});
console.log(uniquesArr);
// To show the hashes
console.log(uniquesObj);
You can use Object.keys() and map() to create key for each object and filter to remove duplicates.
var array = [{
id: 1,
label: 'one'
}, {
id: 1,
label: 'one'
}, {
id: 2,
label: 'two'
}];
var result = array.filter(function(e) {
var key = Object.keys(e).map(k => e[k]).join('|');
if (!this[key]) {
this[key] = true;
return true;
}
}, {});
console.log(result)
You could use a hash table and store the found id.
var array = [{ id: 1, label: 'one' }, { id: 1, label: 'one' }, { id: 2, label: 'two' }],
uniqueProducts = array.filter(function(elem) {
return !this[elem.id] && (this[elem.id] = true);
}, Object.create(null));
console.log('uniqueProducts', uniqueProducts);
Check with all properties
var array = [{ id: 1, label: 'one' }, { id: 1, label: 'one' }, { id: 2, label: 'two' }],
keys = Object.keys(array[0]), // get the keys first in a fixed order
uniqueProducts = array.filter(function(a) {
var key = keys.map(function (k) { return a[k]; }).join('|');
return !this[key] && (this[key] = true);
}, Object.create(null));
console.log('uniqueProducts', uniqueProducts);
You can use reduce to extract out the unique array and the unique ids like this:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
var result = array.reduce(function(prev, curr) {
if(prev.ids.indexOf(curr.id) === -1) {
prev.array.push(curr);
prev.ids.push(curr.id);
}
return prev;
}, {array: [], ids: []});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}
If you don't know the keys, you can do this - create a unique key that would help you identify duplicates - so I did this:
concat the list of keys and values of the objects
Now sort them for the unique key like 1|id|label|one
This handles situations when the object properties are not in order:
var array=[{id:1,label:"one"},{id:1,label:"one"},{id:2,label:"two"}];
var result = array.reduce(function(prev, curr) {
var tracker = Object.keys(curr).concat(Object.keys(curr).map(key => curr[key])).sort().join('|');
if(!prev.tracker[tracker]) {
prev.array.push(curr);
prev.tracker[tracker] = true;
}
return prev;
}, {array: [], tracker: {}});
console.log(result);
.as-console-wrapper{top:0;max-height:100%!important;}

Categories

Resources