lodash chaining groupBy - object

I'm trying to figure out how to convert an array of vehicle objects, that are only unique by trim/year, to an array nested object properties. Originally I was looping through all of the properties to organize the vehicles into a hierarchical structure of arrays i.e., make[model[trim[year[]]]], but I think it would be faster to look up the vehicles by object properties i.e., make.model.trim.year. I'm a lodash noob so I'm not sure how to go about this.
The data returned is structured like this:
[
{
id:1
makeCode:"Make1"
modelCode:"Modela"
selected:true
trimCode:"D"
yearCode:"2014"
},
{
id:2
makeCode:"Make1"
modelCode:"Modela"
selected:true
trimCode:"D"
yearCode:"2015"
},
{
id:3
makeCode:"Make1"
modelCode:"Modela"
selected:true
trimCode:"D"
yearCode:"2016"
},
{
id:4
makeCode:"Make1"
modelCode:"Modela"
selected:true
trimCode:"LX"
yearCode:"2014"
},
{
id:5
makeCode:"Make1"
modelCode:"Modela"
selected:true
trimCode:"LX"
yearCode:"2015"
},
{
id:6
makeCode:"Make1"
modelCode:"Modela"
selected:true
trimCode:"LX"
yearCode:"2016"
},
{
id:7
makeCode:"Make2"
modelCode:"Modelb"
selected:true
trimCode:"D"
yearCode:"2014"
},
{
id:8
makeCode:"Make2"
modelCode:"Modelb"
selected:true
trimCode:"D"
yearCode:"2015"
},
{
id:9
makeCode:"Make2"
modelCode:"Modelb"
selected:true
trimCode:"D"
yearCode:"2016"
}
]
This is working correctly to get the objects ordered by makeCode:
vm.makeGroups = _.groupBy(vm.selectedVehcileTypes, function(v) { return v.makeCode});
but I would like to do this on every level, so I have something like:
vm.makeGroups = _.groupBy(vm.selectedVehcileTypes, function(v) { return v.makeCode});
vm.modelGroups = _.groupBy(vm.makeGroups, function(v) { return v.modelCode});
vm.trimGroups = _.groupBy(vm.modelGroups, function(v) { return v.trimCode});
Basically I want to chain the grouping so that the end result looks like this:
{
Make1: {
modela: {
D: {
'2012': false,
'2013': false
'2014': true // selected
}
}
}
}
}
Any help is appreciated, thanks.

The solution below uses a combination of _.reduce() and _.setWith() as the primary functions to obtain the final result.
// array: Collection of objects
// keysPath: The path of the of the
// property to set check _.setWith docs
// keyValue: The value of each property
function getResult(array, keysPath, keyValue) {
// This is used to get the values from the keysPath
var getValue = _.curry(_.get, 2);
return _.reduce(array, function(result, item) {
// get path
var path = _.map(keysPath, getValue(item));
// set property values, note that using `Object` as a customizer
// makes sure that yearCode is not treated as an index of an array
// but an index of an object
return _.setWith(result, path, item[keyValue], Object);
}, {});
}
var result = getResult(
data,
['makeCode', 'modelCode', 'trimCode', 'yearCode'],
'selected'
);
var data = [
{
id:1,
makeCode:"Make1",
modelCode:"Modela",
selected:true,
trimCode:"D",
yearCode:"2014"
},
{
id:2,
makeCode:"Make1",
modelCode:"Modela",
selected:true,
trimCode:"D",
yearCode:"2015"
},
{
id:3,
makeCode:"Make1",
modelCode:"Modela",
selected:true,
trimCode:"D",
yearCode:"2016"
},
{
id:4,
makeCode:"Make1",
modelCode:"Modela",
selected:true,
trimCode:"LX",
yearCode:"2014"
},
{
id:5,
makeCode:"Make1",
modelCode:"Modela",
selected:true,
trimCode:"LX",
yearCode:"2015"
},
{
id:6,
makeCode:"Make1",
modelCode:"Modela",
selected:true,
trimCode:"LX",
yearCode:"2016"
},
{
id:7,
makeCode:"Make2",
modelCode:"Modelb",
selected:true,
trimCode:"D",
yearCode:"2014"
},
{
id:8,
makeCode:"Make2",
modelCode:"Modelb",
selected:true,
trimCode:"D",
yearCode:"2015"
},
{
id:9,
makeCode:"Make2",
modelCode:"Modelb",
selected:true,
trimCode:"D",
yearCode:"2016"
}
];
function getResult(array, keysPath, keyValue) {
var getValue = _.curry(_.get, 2);
return _.reduce(array, function(result, item) {
var path = _.map(keysPath, getValue(item));
return _.setWith(result, path, item[keyValue], Object);
}, {});
}
var result = getResult(
data,
['makeCode', 'modelCode', 'trimCode', 'yearCode'],
'selected'
);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.js"></script>

Related

How to access specific items in array? [nodejs]

I'm having trouble getting specific items in this array.
[
{
"averages":[
{
"STATUS":"0",
"TYPE":"AAA",
},
{
"STATUS":"0",
"TYPE":"ABC",
},
{
"STATUS":"1",
"TYPE":"ESD",
},
{
"STATUS":"1",
"TYPE":"AAA",
},
]
},
{
"averages":[
{
"STATUS":"0",
"TYPE":"CCC",
},
{
"STATUS":"0",
"TYPE":"AAA",
},
{
"STATUS":"1",
"TYPE":"ESD",
},
{
"STATUS":"1",
"TYPE":"XXX",
},
]
},
{
"averages":[
{
"STATUS":"1",
"TIPO":"XXX",
},
{
"STATUS":"1",
"TYPE":"LLL",
},
{
"STATUS":"1",
"TYPE":"AAA",
},
{
"STATUS":"1",
"TYPE":"NU",
},
{
"STATUS":"0",
"TYPE":"XXX",
},
{
"STATUS":"1",
"TYPE":"AAA",
}
]
},
]
I'm trying to separate status from type AAA
[0, 1, 0, 1, 1, 1]
I get the array like this:
var quality = solicitacoes[0].produtor.dados[0].apiLaticinio[0].qualidade[0]
var qualityjson = JSON.parse(quality)
console.log(JSON.stringify(qualityjson));
I tried to use this material, but without success: enter link description here
What nodejs resource can I access the items in the way I mentioned?
Thanks if anyone can help me!
UPDATE:
I used this method to try to solve my problem.
function getStatus(arr) {
let results = [];
arr.forEach(avg => {
avg.averages.forEach(element => {
if (element["TYPE"] === 'AAA') {
results.push(element["STATUS"])
}
})
})
return results;
}
const results = getStatus(quality)
But I'm getting the following result:
TypeError: arr.forEach is not a function
To get all the status from the given array you can just use Array.map() inside Array.map() like below:
const getItems = (array = items) => {
return array.map((arItem, _) => {
return arItem.averages.map((nestItem, _) => {
return nestItem.STATUS
})
})
}
console.log([].concat(...getItems()))
//Output:
//[
// '0', '0', '1', '1',
// '0', '0', '1', '1',
// '1', '1', '1', '1',
// '0', '1'
//]
However if you are trying to get the items with TYPE='AAA', then you can use the following code:
const getTypeAAAItems = (array = items) => {
return array.map((arItem, _) => {
return arItem.averages.filter((nestItem) => {
return nestItem.TYPE === 'AAA'
})
})
}
//Output:
//[
// { STATUS: '0', TYPE: 'AAA' },
// { STATUS: '1', TYPE: 'AAA' },
// { STATUS: '0', TYPE: 'AAA' },
// { STATUS: '1', TYPE: 'AAA' },
// { STATUS: '1', TYPE: 'AAA' }
//]
I believe you're saying that you want to turn an array of objects with a STATUS field into an array of numbers. Let's say you have a single item from your above array:
{
"averages":[
{
"STATUS":"0",
"TYPE":"AAA",
},
{
"STATUS":"0",
"TYPE":"ABC",
},
{
"STATUS":"1",
"TYPE":"ESD",
},
{
"STATUS":"1",
"TYPE":"AAA",
},
]
},
Assuming the above is parsed and stored as item:
const statuses = item.averages.map(i => i.STATUS);
Array.prototype.map will let you manipulate each element of an array in the same way and return the new array (which will have the same number of elements as the old one).
In this case, we take an object with a STATUS and TYPE field, and instead return only the value of the STATUS field. So we end up with an array of numbers; [0, 0, 1, 1].

How to get least(min) value from array of object?

I have a an array that contains list of json object like mentioned below:
[
{
"id":1,
"type":"type1",
"selling_price":2199,
},
{
"id":2,
"type":"type1",
"selling_price":4999,
},
{
"id":3,
"type":"type1",
"selling_price":6999,
},
{
"id":4,
"type":"type2",
"selling_price":1999,
},
{
"id":5,
"type":"type2",
"selling_price":2399,
},
{
"id":6,
"type":"type2",
"selling_price":2999,
}
]
I want to extract min selling price for only type="type1". the min price should be 2199 for type1
I tried using below method but its not working
_.map(arr, price => {
if(price.type=="type1") {
let pp =_.min(price, function(minPrice){ return minPrice.selling_price; });
console.log(pp)
}
});
Edit code suggested by #VLAZ
data() {
return {
minSellingPrice:'',
arr:[]
}
},
method() {
leastPrice() {
if(this.arr) {
const result = _.chain(arr)
.filter(item => item.type === "type1")
.map('selling_price')
.min()
.value()
this.minSellingPrice=result;
}
else this.minSellingPrice=''
}
}
by default the this.minSellingPrice should be empty but with this code whenever this.minSellingPrice is showing as infinity.
You are doing the mapping wrong. You have the condition inside the callback which only gives you one item at a time. Instead, you should filter() the collection to only the type you want first, then get the min() price.
Underscore has the _.chain() method which allows you to do multiple operations on the same dataset without needing to save intermediate results. To get the result at the end, you need to call .value() to signify no further calls would be chained:
const arr = [ { "id":1, "type":"type1", "selling_price":2199, }, { "id":2, "type":"type1", "selling_price":4999, }, { "id":3, "type":"type1", "selling_price":6999, }, { "id":4, "type":"type2", "selling_price":1999, }, { "id":5, "type":"type2", "selling_price":2399, }, { "id":6, "type":"type2", "selling_price":2999, } ];
const result = _.chain(arr)
.filter(item => item.type === "type1")
.min('selling_price')
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.11.0/underscore-min.js"></script>
If you want just the price, not the object itself, you can use map() to extract just that property:
const arr = [ { "id":1, "type":"type1", "selling_price":2199, }, { "id":2, "type":"type1", "selling_price":4999, }, { "id":3, "type":"type1", "selling_price":6999, }, { "id":4, "type":"type2", "selling_price":1999, }, { "id":5, "type":"type2", "selling_price":2399, }, { "id":6, "type":"type2", "selling_price":2999, } ];
const result = _.chain(arr)
.filter(item => item.type === "type1")
.map('selling_price')
.min()
.value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.11.0/underscore-min.js"></script>
If you want a fallback value in case of an empty array, currently the code will return Infinity, since this is the default return of min. Unfortunately, Underscore doesn't have a comprehensive enough API to easily solve it. One easy way is to just check for that value and substitute it with another for example.
if (result === Infinity) {
result = null;
}
An alternative if you want to use only Undescore is to define your own method via mixin(). Many methods might solve the issue here but as a general helper method, I'd go for or() which will allow you to swap a value with another:
/**
* Generic function that either value or if the value fails a test a fallback
* #param {*} value - value to be tested or substituted
* #param {*} otherValue - fallback in case `value` fails the test
* #param {Function} [test] - what the value would be tested against. By default it fails for null or undefined
*/
_.mixin({
or: function(value, otherValue, test = x => x != null ) {
return test(value) ? value : otherValue ;
}
});
const fullArray = [ { "id":1, "type":"type1", "selling_price":2199, }, { "id":2, "type":"type1", "selling_price":4999, }, { "id":3, "type":"type1", "selling_price":6999, }, { "id":4, "type":"type2", "selling_price":1999, }, { "id":5, "type":"type2", "selling_price":2399, }, { "id":6, "type":"type2", "selling_price":2999, } ];
const emptyArray = [];
function getMinimum(arr) {
return _.chain(arr)
.filter(item => item.type === "type1")
.map('selling_price')
.min()
.or("", _.isFinite) //use the or() mixin and pass a different test function that rejects `Infinity`
.value();
}
const fullResult = getMinimum(fullArray);
const emptyResult = getMinimum(emptyArray);
console.log(`fullResult is [${fullResult}]`);
console.log(`emptyResult is [${emptyResult}]`); //empty string
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.11.0/underscore-min.js"></script>
Lastly, if you like Underscore but find the API a bit limiting, like in this case, I'd suggest switching to Lodash. It is very close to Undescore as it splintered off at some point in the past. Lodash has a bit more consistent interface and a lot more functionality, so it a lot of cases it's a straight upgrade over Underscore or at least on par. The functionality you want expressed in Lodash is this:
const fullArray = [ { "id":1, "type":"type1", "selling_price":2199, }, { "id":2, "type":"type1", "selling_price":4999, }, { "id":3, "type":"type1", "selling_price":6999, }, { "id":4, "type":"type2", "selling_price":1999, }, { "id":5, "type":"type2", "selling_price":2399, }, { "id":6, "type":"type2", "selling_price":2999, } ];
const emptyArray = [];
function getMinimum(arr) {
return _.chain(arr) //no need for .chain() as Lodash implicitly chains via this
.filter(item => item.type === "type1")
.map('selling_price')
.min() //min defaults to `undefined` if nothing is found
.defaultTo(''); // similar to the `or()` mixin above but only for `undefined`, `null`, or `NaN`
.value()
}
const fullResult = getMinimum(fullArray);
const emptyResult = getMinimum(emptyArray);
console.log(`fullResult is [${fullResult}]`);
console.log(`emptyResult is [${emptyResult}]`); //empty string
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
Lodash supports everything needed out of the box to complete the entire operation, unlike Underscore.
You use the function minBy and filter function of lodash
let filter_arr = _.filter(arr, function(o) { return o.type=='type1';})
_.minBy(filter_arr,function(o) {
return o.selling_price;
});
It will give as below result
{id: 1, selling_price: 2199, type: "type1"}
Hope it will help you

Mongoose does not update values

I am trying to update a field's value in my MongoDB with mongoose. However, it does not update it correctly.
Here is what I have so far:
Test.findById(987456, function(err, doc) {
if (doc) {
var map = doc.data;
map['2019-07-07'] = {
TAS: "111",
TWS: "222",
TSWD: "333"
}
doc.set('data', map)
doc.save((err, doc1) => {
if(err) console.log(err);
else{
console.log(doc1)
}
});
}
})
This is my Schema
var test = new Schema({
data: { type: Schema.Types.Mixed, default: {} },
})
var Test = mongoose.model('test', test);
The data prior to updating inside the data field is { '2019-06-06': { TWS: '4', TAS: '27', TSWD: '33' }
The code does not throw any errors and console.log(doc1) inside the save callback prints the correct value { '2019-06-06': { TWS: '4', TAS: '27', TSWD: '33' },'2019-07-07': { TAS: '111', TWS: '222', TSWD: '333' }}
However, when I check MongoDB Atlas, the value is not changed at all.
Just use findOneAndUpdate:
let query = { id: 987456 } // or whatever your id / _id is
let update = { data: { TAS: "111", TWS: "222", TSWD: "333" }}
Test.findOneAndUpdate(query, update, function(err, doc) {
console.log(doc)
})

How to query property keys with conditions in LoopBack

How to query the key values of properties in LoopBack? In my case, the keys are array of objects.
The schema I generated is,
{
"bookingsLog": [
{
"checkIn": 1456079400000,
"checkOut": 1456165800000
},
{
"checkIn": 1456079400000,
"checkOut": 1456165800000
}
]
}
The remote method used to query is,
Resort.search = function(custom, cb) {}
Resort.remoteMethod('search', {
accepts: {
arg: 'custom',
type: 'object',
http: function(ctx) {
var _cIn = ctx.req.body.checkIn;
var _cOut = ctx.req.body.checkOut;
Resort.find({
where: {
and: [{ checkIn: { neq: _cIn } }, { checkOut: { neq: _cOut } },
{ checkIn: { neq: { between: [_cIn, _cOut] } } },
{ checkOut: { neq: { between: [_cIn, _cOut] } } }
]
}
}, function(err, resorts) {
console.log('Length is = ' + resorts.Length);
console.log('Res is = ' + JSON.stringify(resorts));
if(err){return ctx.res.send(err);}
if(resorts){return ctx.res.send(resorts);}
});
}
},
returns: {
arg: 'custom',
type: 'object'
}
});
Thanks in advance!

Mongoose array indexOf not working as it should

I've got a document in mongoose which looks like this:
{
"_id":ObjectId("553e4d7a1ebb424364e47e0f"),
"url":"/api/images/553e4d6f1ebb424364e47e0b",
"type":"knowledge",
"title":"asdasd",
"week":18,
"fields":{
"end":"sdajlsdjlfnsdf",
"correctAnswer":"2",
"answers":{
"0":"ljnsdlfjnsdfn",
"1":"lnsldfnlsf",
"2":"ljsndlfnsdlf"
},
"question":"sjlnasjld"
},
"participants":[
{
"user":ObjectId("553e4d531ebb424364e47e07"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e4e3bb4ad1bbb646a545c"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e4ecc007b12e464c6a7ff"),
"result":{
"answer":"1"
}
},
{
"user":ObjectId("553e4ecc007b12e464c6a7ff"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e5239b1d44ec7681bcb66"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e52be4d85843e69828df3"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e52cd8dded86469ee3974"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e537b6c4981b169a10c3d"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e5553afb1fd6e6a90e6af"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e55ddda5144976aa90224"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e563af09b82e46ac30c0c"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e563af09b82e46ac30c0c"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e563af09b82e46ac30c0c"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e57b6d554ab136caa95a8"),
"result":{
"answer":"2"
}
},
{
"user":ObjectId("553e5a7c094e51826d88dbff"),
"result":{
"answer":"2"
}
}
]
}
And what I'm trying to do is to restrict 'participants' from participating in the mission if they've already completed it. I'm using this code to try to restrict them but it's not working as it should:
if (mission.participants && mission.participants.map(function (e) { return e.user; }).indexOf(uid) > -1) {
return res.send(403, { err: "You've already completed this mission" });
}
No matter what I try the indexOf function always returns -1
If I run console.log(mission.participants.map(function (e) { return e.user; })); I get
[ 553e4d531ebb424364e47e07,
553e4e3bb4ad1bbb646a545c,
553e4ecc007b12e464c6a7ff,
553e4ecc007b12e464c6a7ff,
553e5239b1d44ec7681bcb66,
553e52be4d85843e69828df3,
553e52cd8dded86469ee3974,
553e537b6c4981b169a10c3d,
553e5553afb1fd6e6a90e6af,
553e55ddda5144976aa90224,
553e563af09b82e46ac30c0c,
553e563af09b82e46ac30c0c,
553e563af09b82e46ac30c0c,
553e57b6d554ab136caa95a8,
553e5a7c094e51826d88dbff ]
I have verified that the uid variable is actually in the array, so that's not the issue.
Running io.js v1.8.1, mongoose v4.0.1, MongoDB v2.6.9
The problem is that you are comparing an object Schema#ObjectId to a String. You need to convert the ObjectId to String in your map function, so the indexOf can find the desired uid:
if (mission.participants && mission.participants.map(function (e) { return e.user.toString(); }).indexOf(uid) > -1) {
return res.send(403, { err: "You've already completed this mission" });
}

Resources