How to use javascript lodash uniqBy on a nested attribute - javascript

I have an object that is structured similar as follows (simplified version):
{
"time": 100,
"complete" : true,
"results" : {
"total": 10,
"score": 3,
"results": [
{
"id" : 123,
"name": "test123"
},
{
"id" : 123,
"name": "test4554"
}
]
}
}
How do I use lodash ._uniqBy to deduplicate the results, based on results.results.id being the unique key?
To clarify, I would like the deduplicated resultset to be returned within the original object structure, e.g.
{
"time": 100,
"complete" : true,
"results" : {
"total": 10,
"score": 3,
"results": [
{
"id" : 123,
"name": "test123"
}
]
}
}
thanks

You can achieve your goal by simply passing the right part of your object into _.uniqBy(array, [iteratee=_.identity]) function.
Next thing you want to do is to 'concat' lodash uniqBy result and your object. This is a little bit tricky. I suggest you to use ES6 Object.assign() method and spread operator.
Check out my solution. Hope this helps.
const myObj = {
"time": 100,
"complete" : true,
"results" : {
"total": 10,
"score": 3,
"results": [
{"id" : 123, "name": "test123"},
{"id" : 123, "name": "test4554"}
]
}
};
const uniq = _.uniqBy(myObj.results.results, 'id');
const resultWrapper = Object.assign({}, myObj.results, { results: [...uniq] });
const resultObj = Object.assign({}, myObj, { results: resultWrapper });
console.log( resultObj );
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>

You can use something like this
const myObj = {
time: 100,
complete : true,
results : {
total: 10,
score: 3,
results: [
{id : 123, name: "test123"},
{id : 123, name: "test4554"}
]
}
};
_.set(myObj, 'results.results', _.uniqBy(_.get(myObj, 'results.results'), 'id'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>

Related

how to change JSON Nested array property "project_id" with "project_name"

Trouble:
[
{
"project_id": 1,
"project_name": "CDP",
"role": "PL"
},
{
"project_id": 2,
"project_name": "Admincer",
"role": "PM"
},
I want to add the "project_id" property from the above three properties to another array using some method.
My idea is: 1. First of all, if I could copy the "project_id" property of this array to the second Nested JSON array, it would be fine.
What I looked up:
const obj = {
"project_id": 1,
"project_name": "CDP",
"role": "PL"
};;
const objCopy = {
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": 1, 1,
"work_day_id": 45,
"time_cards": [
{
... obj
}
]
};;
console.log (objCopy);
I found that I could copy it this way.
I tried the above code in Chrome Console.
The array was copied, but the entire object was copied. I just want to copy the properties of project_id.
I want to create a new property called "prj_name" in this array and display only that property in Vuetify.
async fetchWorkerTimeCard() {
try {
this.worker_data = []
await this.$axios.$get('/worker_time_card', {
params: {
work_date: this.calendarVal
}
}).then(data => {
this.worker_data = data
})
var projects = await this.fetch_worker_projects()
console.log(projects)
} catch (error) {
console.log(error)
this.worker_data = []
}
},
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.5/vue.js"></script>
<v-card>
<v-data-table v-if="worker_data.time_cards" :headers="headers2" :items="worker_data.time_cards"></v-data-table>
</v-card>
You can simply change your object data like any other object in JS.
const obj = {
"project_id": 1,
"project_name": "CDP",
"role": "PL"
};
const objCopy = {
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": 1,
"work_day_id": 45
}
console.log({...obj, ...objCopy})
This will create 1 object that merged.
Or if you just want to project_id value then just change it like:
objCopy.project_id = obj.project_id
If I'm understanding your first question correctly, you might be interested in the map function, which allows you to create a new array from an existing array. So, for example, if the first snippet you posted is an array of objects we call projects, you could use:
var projectIds = projects.map(p => p.project_id), where projectIds would now be an array of just project ids.
It seems like you might be asking more than this though, so I second Bravo's request for more clarification/reorganization in your question.
I'm not pretty sure if you want either of the following results:
{
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": [
1,
1
],
"work_day_id": 45,
"time_cards": [
{
"project_id": 1
},
{
"project_id": 2
}
]
}
or this
{
"start_time": "09:00:00",
"end_time": "18:00:00",
"rest_time": "01:00:00",
"worked_time": "08:00:00",
"is_wfh": true,
"id": [
1,
1
],
"work_day_id": 45,
"time_cards": [
"project_id": [1, 2]
}
In case you need the first scenario, the following code may help you:
// This function return an array with: [{project_id: Number}]
function onlyIds(obj) {
const ids = [];
// Iterate the obj Array
obj.forEach(element => {
// Push a new JSON: "{project_id: 1}" or whatever
ids.push({ project_id: element.project_id });
});
// return an array that only contains the project_id
return ids;
}
const obj = [
{
project_id: 1,
project_name: 'CDP',
role: 'PL',
},
{
project_id: 2,
project_name: 'Admincer',
role: 'PM',
},
];
const objCopy = {
start_time: '09:00:00',
end_time: '18:00:00',
rest_time: '01:00:00',
worked_time: '08:00:00',
is_wfh: true,
id: [1, 1],
work_day_id: 45,
time_cards: onlyIds(obj),
};
console.log(onlyIds(obj));
console.log(objCopy);
I'm pretty sure there should be any more elegant/optimal way (as using any kind of higher-order function I may be missing right now) but as far as I understood, this should do the job.

Sort max date from Object

Trying to sort the object with the max date. One id may have multiples dates. Below is the format of the object where id:123 has two dates. So I am trying to take the max date for the user 123. I used the sort method and storing the array[0] but still there is something missing.
var arr = [
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
}
],
"id": "3212"
},
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
},
{
"score": 20,
"date": "2021-05-05T00:00:00"
}
],
"id": "123"
},
{
"scores": [
{
"score": 5,
"date": "2021-05-05T00:00:00"
}
],
"id": "321"
}
]
What I tried is
_.each(arr, function (users) {
users.scores = users.scores.filter(scores => new Date(Math.max.apply(null, scores.date)));
return users;
});
Expecting the output to look like the following with the max date selected.
[
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
}
],
"id": "3212"
},
{
"scores": [
{
"score": 10,
"date": "2021-06-05T00:00:00"
}
],
"id": "123"
},
{
"scores": [
{
"score": 5,
"date": "2021-05-05T00:00:00"
}
],
"id": "321"
}
]
Your filter callback function is not performing a comparison to filter the correct element. Also, although applying the "maximum" algorithm on the dates as string would be fine in your case (because of the date format you have), it would be much safer to transform the date strings into date objects to consistantly get correct results regardless of the format.
In the solution below, you can use a combination of Array.map() and Array.sort() to copy and process your data in the correct result.
const data = [{
'scores': [{
'score': 10,
'date': '2021-06-05T00:00:00'
}],
'id': '3212'
}, {
'scores': [{
'score': 10,
'date': '2021-06-05T00:00:00'
}, {
'score': 20,
'date': '2021-05-05T00:00:00'
}],
'id': '123'
}, {
'scores': [{
'score': 5,
'date': '2021-05-05T00:00:00'
}],
'id': '321'
}];
// map the data and return the updated objects as the result
const result = data.map((user) => {
// copy the scores array to not mutate the original data
const sortedScores = user.scores.slice();
// sort the scores array by date descending
sortedScores.sort((a, b) => (new Date(b.date) - new Date(a.date)));
// return the same user with the first score from the sorted array
return {
...user,
scores: [sortedScores[0]]
};
});
console.log(result);

Filtering and returning parent object using lodash chain

I want to use Lodash chain function to filter through an arrays nested array items and then return the full parent object.
Here is some dummy data from my use case to illustrate my issue:
const capital = [
{
"financeCategory": "Loans",
"financeCategoryId": "22HM6fFFwx9eK2P42Onc",
"financeElements": [
{
"financeCategoryId": "22HM6fFFwx9eK2P42Onc",
"financeElementId": "JQiqqvGEugVQuI0fN1xQ",
"financeElementTitle": "Convertible loan",
"data": [
{
"month": 1,
"value": 100,
"year": "2020"
},
{
"month": 1,
"value": 100,
"year": "2019"
},
],
}
]
},
{
"financeCategory": "Investments",
"financeCategoryId": "JtnUsk5M4oklIFk6cAlL",
"financeElements": []
},
{
"financeCategory": "Ownerships Contribution",
"financeCategoryId": "PaDhGBm5uF0PhKJ1l6WX",
"financeElements": []
}
];
I want to filter on the "data" array within the financeElements and then return full expense object with the filter applied on "data".
Let's say I want to manipulate the expense object and only get the data on the financeElements that have the year 2020. I've tried like so:
const expenseFiltered: any = _.chain(expenses)
.flatMap('financeElements')
.flatMap('data')
.filter({year: '2020' as any}).value();
But that just gives me the filtered "data" objects.
Output:
[{
"month": 1,
"value": 100,
"year": "2020"
}]
Now I know there are ways that I could use that to produce the full object with the filtered data, but I really want to do this in just one simple _.chain command
Desired output
[
{
"financeCategory": "Loans",
"financeCategoryId": "22HM6fFFwx9eK2P42Onc",
"financeElements": [
{
"financeCategoryId": "22HM6fFFwx9eK2P42Onc",
"financeElementId": "JQiqqvGEugVQuI0fN1xQ",
"financeElementTitle": "Convertible loan",
"data": [
{
"month": 1,
"value": 100,
"year": "2020"
}
],
}
]
},
{
"financeCategory": "Investments",
"financeCategoryId": "JtnUsk5M4oklIFk6cAlL",
"financeElements": []
},
{
"financeCategory": "Ownerships Contribution",
"financeCategoryId": "PaDhGBm5uF0PhKJ1l6WX",
"financeElements": []
}
]
Is this possible using lodash chain?
A chain is used to transform a structure in several steps. In your case, you don't want to change the structure. You can use nested Array.map() (or lodash's _.map()) calls to iterate and rebuild the structure, and internally _.filter() the data:
const capital = [{"financeCategory":"Loans","financeCategoryId":"22HM6fFFwx9eK2P42Onc","financeElements":[{"financeCategoryId":"22HM6fFFwx9eK2P42Onc","financeElementId":"JQiqqvGEugVQuI0fN1xQ","financeElementTitle":"Convertible loan","data":[{"month":1,"value":100,"year":"2020"},{"month":1,"value":100,"year":"2019"}]}]},{"financeCategory":"Investments","financeCategoryId":"JtnUsk5M4oklIFk6cAlL","financeElements":[]},{"financeCategory":"Ownerships Contribution","financeCategoryId":"PaDhGBm5uF0PhKJ1l6WX","financeElements":[]}];
const expenseFiltered = capital.map(ex => ({
...ex,
financeElements: ex.financeElements.map(fe => ({
...fe,
data: _.filter(fe.data, { year: '2020' })
}))
}));
console.log(expenseFiltered);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Get nested object - but identifier is part of needed object

How do I get a nested object from a selected document?
If this is my document, which I get with Collection.findOne({ _id: 'dZXr2Pg7Ak4M5aYWF'})...
{
"_id" : "dZXr2Pg7Ak4M5aYWF",
"points" : [
{
"id" : "Gwf5BzorXyYBZSEzK",
"coordinates" : [
433,
215
],
"content" : "anything"
},
{
"id" : "iwSM98W5PD87BtcLa",
"coordinates" : [
666,
186
]
}
]
}
... I need to get the complete data of the point with the id Gwf5BzorXyYBZSEzK. So my result should look like:
result = {
"id" : "Gwf5BzorXyYBZSEzK",
"coordinates" : [
433,
215
],
"content" : "anything"
}
You can simply filter the points array, with Array.prototype.filter, like this
console.log(data.points.filter(function(obj) {
return obj.id === "Gwf5BzorXyYBZSEzK";
})[0]);
// { id: 'Gwf5BzorXyYBZSEzK' coordinates: [ 433, 215 ], content: 'anything' }
We just take the first element from the filtered result array. If you want to get all the elements, which have the specific id, then just remove the subscript.
If your environment supports ECMAScript 2015's Arrow functions, then you can write the same as
console.log(data.points.filter((obj) => obj.id === "Gwf5BzorXyYBZSEzK")[0]);
var data = {
"_id": "dZXr2Pg7Ak4M5aYWF",
"points": [{
"id": "Gwf5BzorXyYBZSEzK",
"coordinates": [433, 215],
"content": "anything"
}, {
"id": "iwSM98W5PD87BtcLa",
"coordinates": [666, 186]
}]
};
function finder(id){
for(i=0;i<data.points.length;i++){
if(data.points[i].id==id){
return data.points[i];
}
}
}
var result = finder("Gwf5BzorXyYBZSEzK");
document.write(JSON.stringify(result));

MongoDB Limit fields and slice projection together

I have the following User object:
{
"_id" : ObjectId("someId"),
"name" : "Bob",
"password" : "fakePassword",
"follower" : [...],
"following" : [..]
}
I need to paginate over the follower list, so I use the slice projection operator, but I just need the paginated followers list to be returned. And I don't know if I am doing it the wrong way, or this can't be done, but limit fields doesn't work with slice projection.
Following are a couple of queries I tried:
collection.findOne(
{
_id: new ObjectId(userId)
},
{
follower: {$slice:[skip, parseInt(pageSize)]},
follower: 1
},..
and
collection.findOne(
{
_id: new ObjectId(userId)
},
{
follower: 1,
follower: {$slice:[skip, parseInt(pageSize)]}
},
But these return all the values in the object, and does not limit the fields, although, the slice works fine in both the cases.
Also when I do something like _id:0,following:0 , this part works, but I don't want to mention each and every field in the query like this, it may create problems once I decide to change the schema.
How do I get this to work, what could be the syntax for the query to get this working..??
Not sure I'm getting your usage pattern here. Perhaps we can simplify the example a little. So considering the document:
{
"_id" : ObjectId("537dd763f95ddda3208798c5"),
"name" : "Bob",
"password" : "fakePassword",
"follower" : [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K"
]
}
So the simple query like this:
db.paging.find(
{ "name": "Bob" },
{
"_id": 0,
"name": 0,
"password": 0,
"follower": { "$slice": [0,3] }
}).pretty()
Gives results:
{
"follower" : [
"A",
"B",
"C"
]
}
And similarly from the following page:
db.paging.find(
{ "name": "Bob" },
{
"_id": 0,
"name": 0,
"password": 0,
"follower": { "$slice": [3,3] }
}).pretty()
Gives the results:
{
"follower" : [
"D",
"E",
"F"
]
}
So for me personally I am not sure whether you were asking about the field exclusion or whether you were asking about "paging" the array results, but either way, both of those examples are shown here.
One way is to actually use _id here by saying {_id:1}:
{ "_id" : ObjectId("537de1bc08eb9d89a7d3a1b2"), "f" : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20 ], "d" : 1 }
> db.test.findOne({ "_id" : ObjectId("537de1bc08eb9d89a7d3a1b2")},{f:{$slice:[0,2]}})
{
"_id" : ObjectId("537de1bc08eb9d89a7d3a1b2"),
"f" : [
1,
2
],
"d" : 1
}
> db.test.findOne({ "_id" : ObjectId("537de1bc08eb9d89a7d3a1b2")},{_id:0, f:{$slice:[0,2]}})
{ "f" : [ 1, 2 ], "d" : 1 }
> db.test.findOne({ "_id" : ObjectId("537de1bc08eb9d89a7d3a1b2")},{_id:1, f:{$slice:[0,2]}})
{ "_id" : ObjectId("537de1bc08eb9d89a7d3a1b2"), "f" : [ 1, 2 ] }

Categories