How to combine a single array of objects based on multiple properties - javascript

Utilizing javascript, I have an array of objects that looks like this:
id | date | store | type | txn | failed
------------------------------------------------
1 | 10-02-18 | 32 | short | 4 | false
2 | 10-02-18 | 32 | long | null | true
3 | 10-03-18 | 32 | short | 7 | false
4 | 10-03-18 | 32 | long | 10 | false
I want to be able to transform this array into something that looks like this:
[
{
date: 10-02-18,
store: 32,
short: {
txn: 4,
failed: false,
},
long: {
txn: null,
failed: true,
},
},
{
date: 10-03-18,
store: 32,
short: {
txn: 7,
failed: false,
},
long: {
txn: 10,
failed: true,
},
}
]
You can see I would like to combine the "type", "txn" and "failed" properties with row that have the same "date" and "storeId", adding the "type" as a property and "txn" and "failed" as child properties of the "type". The "id" property could be ignored in the new array.
I use lodash quite a bit but that isn't a requirement for this. I'm just struggling to wrap my head around how to do this transformation.

You basically just need to create an object with keys that represent something unique to the groups you want. You could make keys that are concatenations of store_date for example and the object will only have one of those and it will be quick to get if you have the store and date. You can build an object like this with reduce. Once you have the object, you can simply call Object.values to get the array of values. For example:
let arr = [
{id:1, date: "10-02-18",store: 32, type: "short", tx: 4, failed: false},
{id:2, date: "10-02-18",store: 32, type: "long", tx: null, failed: true},
{id:3, date: "10-03-18",store: 32, type: "short", tx: 7, failed: false},
{id:4, date: "10-03-18",store: 32, type: "long ", tx: 10, failed: false}
]
let obj = arr.reduce((obj, {id, date, store, type, tx, failed}) => {
// make a unique key
key = `${date}_${store}`
// if we haven't seen this key add it with the outline of the object
if(!obj[key]) obj[key] = {date, store}
// add specific type to either the found obj or the new one
obj[key][type] = {tx, failed}
return obj
}, {})
// obj is an object keyed to date_store
// get just the values
console.log(Object.values(obj))

Related

How to filter multiple properties of one array of object from the other array of object?

I am trying to solve the problem in which i have to apply multiple filters to the array of object. Let suppose I am having a larger array of object which contains the configuration property which is further an object. On other side i have small object which are the ones the user chooses to filter(based on the checkboxes). i want to compare objects made with the parent array of objects by selecting multiple values.
So in the image the user chooses multiple values(using check boxes) and based on that he needs to filter the main array of objects.So after checking the checkboxes i get childObject and i have to filter parentArray on the basis of that..... please help me with this:
childobject =
{'Bathroom': '[2,1]',
'Bedroom': '[3,2]',
'halfBathroom':'0',
'name':'[2BD-2BA,2BD-2BA-1]'}
parentArray = [
0:{},
1:{},
2:{
'property1':'____',
'property2':'_____',
'configuration':'{
bathroom: 2
bedroom: 2
created_at: "2019-03-08 20:52:52"
created_by: 264
half_bathroom: 1
id: 26
is_selected: 0
name: "2BD-2BA-1/2BA"
name_en: "2BD-2BA-1/2BA"
name_es: "2RE-2BA-1/2BA"
status: 1
updated_at: "2019-08-23 05:39:44"
}'
}
3: {},
4:{}
]
I had to update the datastructure at some points:
You had different key in child and parent (upper/lowercase + camelcase/_ writing)
Some Missing } in the parent.
In child quotationmarks for integer deleted.Missing , added.
Changing some values in cruiteria, so that there is a result.
In parent delting of 0:, 1:, 2:, 3:, 4: to get a valid array.
childArray = {
'bathroom': [2,1],
'bedroom': [3,2],
'half_bathroom':1,
'name':['2BD-2BA', '2BD-2BA-1/2BA']
};
parentArray = [
{},
{},
{
'property1':'____',
'property2':'_____',
'configuration':{
bathroom: 2,
bedroom: 2,
created_at: "2019-03-08 20:52:52",
created_by: 264,
half_bathroom: 1,
id: 26,
is_selected: 0,
name: "2BD-2BA-1/2BA",
name_en: "2BD-2BA-1/2BA",
name_es: "2RE-2BA-1/2BA",
status: 1,
updated_at: "2019-08-23 05:39:44"
},
},
{},
{}
]
let res = parentArray.filter(elem => Object.entries(childArray).every(([key,val]) => {
let conf = elem.configuration;
if (conf===undefined) return false;
if (typeof(val) === 'object') {
return val.some(crit => crit===conf[key]);
} else {
return val===conf[key];
}
}));
console.log(res);

Joi validating list of objects where all must contain optional key if it is in one object

I have a list of objects with the following required keys:
Date, Time, Price
I want to add an optional key "order" and if one of these objects contains that optional key, they all must. How would I go about validating that with joi?
You can create a schema dynamically depending on the array that is going to be validated. If the array has some objects that have order property then schema should require this property from every object in the array, otherwise, schema should treat this property as optional:
const schemaFactory = input =>
Joi.array().items(
Joi.object().keys({
Date: Joi.required(),
Time: Joi.required(),
Price: Joi.required(),
order: input.some(item => item.hasOwnProperty('order'))
? Joi.required()
: Joi.optional()
})
)
const input = [
{ Date: 1, Time: 2, Price: 3 },
{ Date: 1, Time: 2, Price: 3, order: true },
]
const schema = schemaFactory(input)
const result = schema.validate(input)
if (result.error) {
console.log(result.error)
}
<script src="https://cdn.jsdelivr.net/npm/joi-browser#13.4.0/dist/joi-browser.min.js"></script>

Javascript Array as More Readable Array

I have a data set like following table.
+------+---------+----+----+----+----+-------+----------+
| Year | Subject | A | B | C | F | Total | PassRate |
+------+---------+----+----+----+----+-------+----------+
| 2015 | Maths | 12 | 20 | 10 | 5 | 47 | 80 |
| 2015 | Sinhala | 18 | 14 | 5 | 10 | 47 | 75 |
| 2016 | Maths | 25 | 15 | 4 | 8 | 52 | 25 |
| 2016 | Sinhala | 20 | 12 | 2 | 18 | 52 | 60 |
+------+---------+----+----+----+----+-------+----------+
I want to store those data in JavaScript array. So I have following code.
var firstArray = [];
firstArray.push(['Year', 'Subject', 'A', 'B', 'C', 'F', 'Total', 'PassRate']); // headers
firstArray.push([2015, 'Maths', 12, 20, 10, 5, 47, 80]); // 1st row
firstArray.push([2015, 'Sinhala', 18, 14, 5, 10, 47, 75]) // 2nd row
console.log(firstArray);
If I need to read how many "B",s in Maths for 2015, I need to run firstArray[1][3].
That is not readable. I mean it is hard to find what it means firstArray[1][3].
So is there way to build my array more readable way like firstArray[2015]['maths'] if I want to read how many "B",s in Maths for 2015
Sounds like you want an object indexed by year, containing objects indexed by subject:
const years = {
'2015': {
Maths: {
A: 12, B: 20, C: 10, F: 5, Total: 47, PassRate: 80
},
Sinhala: {
A: 18, B: 14, C: 5, F: 10, Total: 47, PassRate: 75
},
},
'2016': {
// ...
}
}
console.log(years['2015'].Maths);
Your purpose is correct, readability of code is very important.
It's not easy and there is no right path to follow, but I try to give you some hints on how to change your code.
First point: naming.
This is very hard, and often even experienced developer need to rename variables as they can't get the proper name at the first time.
Your variable is firstArray and this of course have low meaning and you can just say it is an array and it is the first...
Nothing about what the array is containing.
A better name could be studentsScoreByYear.
The improvement of this name is that it try to address the meaning of the content.
Then the index ad magic numbers.
Of course you need numbers to get a field from an array, and if you just use the proper integer in the code is very complicated to remember what this field is.
A way is to avoid array and use hash map, in javascript plain objects.
So you can give to each field a name.
If you can't abbandon the array for whatever reason, you can improve here too, just use constants to save the proper indexes:
var MATHS = 1;
var SCORE_OF_B = 3;
var studentsScoreByYear= [
['Year', 'Subject', 'A', 'B', 'C', 'F', 'Total', 'PassRate'],
[2015, 'Maths', 12, 20, 10, 5, 47, 80],
[2015, 'Sinhala', 18, 14, 5, 10, 47, 75]
];
console.log(studentsScoreByYear[MATHS][SCORE_OF_B]);
There are other ways of course, this is just a possibility.
A list of objects is what you want to represent your data in a readable format.
As for selecting data:
You can filter an array by using the filter method on the array.
You can change the contents or isolate a parameter with the map method on the array.
You can perform arithmetic operations with the reduce method on the array.
var data = [
{ year: 2015, subject: 'Maths', a: 12, b: 20, c: 10, f: 5, total: 47, passrate: 80 },
{ year: 2015, subject: 'Sinhala', a: 18, b: 14, c: 5, f: 10, total: 47, passrate: 75 },
{ year: 2016, subject: 'Maths', a: 25, b: 15, c: 4, f: 8, total: 52, passrate: 25 },
{ year: 2016, subject: 'Sinhala', a: 20, b: 12, c: 2, f: 18, total: 52, passrate: 60 },
];
console.log("Present All:");
console.log(data
.map(function (row) {
return [row.year, row.subject, row.a, row.b, row.c, row.f, row.total, row.passrate].join(", ");
})
.join("\n"));
console.log("Count `B` in `Maths` in 2015");
console.log(data
.filter(function (row) { return row.year === 2015 && row.subject === "Maths"; })
.map(function (row) { return row.b; })
.reduce(function (accumulator, currentValue) { return accumulator + currentValue; }, 0));
Sidenote
The eagle-eyed out there has probably already spotted that the map and filter calls are redundant in the "Present All" example as i could simply have put:
data.reduce(function (accumulator, row) {
return accumulator + (row.year === 2015 && row.subject === "Maths" ? row.b : 0);
}, 0);
While the above code is "smarter", i think the more verbose answer using map and filter will be of more use to anyone trying to learn JavaScript array handling and JavaScript objects.

JavaScript - Add properties with different values to objects in array then render as HTML table

Goal: I would like to take a list of n strings, add referential values, and inject them into HTML as rows in a table.
--- Details ---
I want to start with a list of n strings:
'object1'
'object2'
'object3'
...etc.
I then want to use those strings to figure out their properties by using them to match other data from API calls:
name: 'object1', id: 00112233, count: 25,
name: 'object2', id: 266924, count: 12884,
name: 'object3', id: 312011045, count: 8,
...etc.
Lastly, I want to render those objects and their properties in a table of n rows on a website:
Name | ID | Count |
--------|-----------|---------|
object1 | 00112233 | 25 |
object2 | 266924 | 12884 |
object3 | 312011045 | 8 |
...etc. | ...etc. | ...etc. |
--- My Thought Process ---
I was thinking that it could make sense to store the original list of n strings as an array:
array = [
'object1'
'object2'
'object3'
//...etc.
]
Then use that array to create n objects inside another array:
objArray = array.map(e => {
return{name: e};
});
Then somehow loop through that array of objects to add properties and individual values:
for (var i = 0; i <= objArray.length; i++) {
objArray[i].id = <id value from API that corresponds with each object name>;
objArray[i].count = <count value from API that corresponds with each object name>;
}
Hopefully we'd arrive at something like this, which could then be injected into an HTML table or something?:
objArray = [
{name: 'object1', id: 00112233, count: 25},
{name: 'object2', id: 266924, count: 12884},
{name: 'object3', id: 312011045, count: 8},
//etc...
]
--- TLDR ---
Goal: I would like to take a list of n strings, add referential values, and inject them into HTML as rows in a table.
Am I on the right track with my thought process? Is there a more efficient way to do this that I'm missing?
How do I loop through objects in an array to add additional properties with different values for each object?
How do I inject an array of objects into an HTML table?
Thanks so much! This is my first question asked here, so please let me know if there's etiquette I'm not following.
OP here! In case anyone stumbles on this thread, my issue was that I didn't need to consider the following step at all, and it was in fact far easier to do something different:
Then use that array to create n objects inside another array:
objArray = array.map(e => {
return{name: e};
});
What I did was scrap trying to map the string array to an object, and instead I directly pushed my results from the API into the array with the following:
for (let i = 0; i < stringList.length; i++) {
$.ajax({
'url': <api url> ,
'headers': <api headers> ,
'success': function (functionObj2) {
objArray.push({
"name": <name from api>,
"count": <count from api>
});
}
});
}
The root of my issue was that I didn't have the objArray.push section correct, and it was overwriting itself. I had essentially mis-attributed this problem to my normal JavaScript, rather than realizing it was a problem inside my ajax.
right way for thought process
can look
<div id="tablebelow">
</div>
var objArray = [
{name: 'object1', id: 00112233, count: 25},
{name: 'object2', id: 266924, count: 12884},
{name: 'object3', id: 312011045, count: 8}
]
var tablehtml = "<table><tr><th></th> <th>Name </th><th>id</th><th>Count </th></tr>" ;
$.each(objArray,function(key,value){
tablehtml += "<tr><td>"+ value.name + "</td>" ;
tablehtml +="<td>"+value.id + "</td>" ;
tablehtml += "<td>"+ value.count + "</td></tr>" ;
});
$("#tablebelow").html(tablehtml);

Group By - Angular JS

I have the following JSON format, and I am looking to combine the "Virtual" and "Physical" objects into one by grouping by cluster and idc
[
{
"_id": {
"cluster": 1,
"idc": "LH8",
"type": "Virtual"
},
"SumCores": 112,
"SumMemory": 384
},
{
"_id": {
"cluster": 1,
"idc": "LH8",
"type": "Physical"
},
"SumCores": 192,
"SumMemory": 768
},
{
"_id": {
"cluster": 2,
"idc": "LH8",
"type": "Virtual"
},
"SumCores": 232,
"SumMemory": 469
},
{
"_id": {
"cluster": 2,
"idc": "LH8",
"type": "Virtual"
},
"SumCores": 256,
"SumMemory": 1024
}
Currently I have all of the output to screen using ng-repeat:
<div ng-repeat="item in servers | orderBy:['idc','cluster'] "><p>IDC:{{item._id.idc}} - Cluster: {{item._id.cluster}} - Type: {{item._id.type}} - Sum Cores: {{ item.SumCores }} </p></div>
Which produces something similar to:
IDC: LH8 - Cluster: 1 - Type: Virtual - Sum Cores: 192
IDC: LH8 - Cluster: 1 - Type: Physical -Sum Cores: 112
IDC: LH8 - Cluster: 2 - Type: Virtual - Sum Cores: 256
IDC: LH8 - Cluster: 2 - Type: Physical -Sum Cores: 232
Ideally I want to group this into a table with this as the ideal format:
+---------+--------------------+--------------------+
| Cluster | LH5 | LH8 |
+---------+--------------------+--------------------+
| | Physical | Virtual | Physical | Virtual |
+---------------------------------------------------+
| 1 | Value | Value | Value | Value |
| 2 | Value | Value | Value | Value |
| 3 | Value | Value | Value | Value |
| 4 | Value | Value | Value | Value |
+---------+----------+---------+----------+---------+
Obviously there is a lot more data than in my sample and value would represent the SumCores.
I also have access to the controller if you think the change would be better made in there:
Machine.aggregate( [ { $match : { $and: [ {"idc": req.query.idc }, {"customer":req.query.customer} ] } } ,{"$group":{_id: {"cluster":"$cluster","idc":"$idc","type":"$type"},"SumCores":{"$sum":"$cores"},"SumMemory": { "$sum":"$memory" }}}, { $sort : { idc : -1, cluster: 1 } } ]).exec(function(err, agg) {
res.json(agg);
});
Fiddle here: http://jsfiddle.net/8n227L2o/
I've forked your Fiddle, and now I've used Underscore.js to group and filter your data according to your example table.
http://jsfiddle.net/pdc5rvyo/1/
It is quite basic, and uses nested tables. You should be able to customize it by allowing uses to change the order of the lists for example.
Code example:
var lhSortedList = _.groupBy(servers, function(item) {
return item._id.idc;
});
$scope.lh8Virtual = _.filter(lhSortedList['LH8'], function(item) {
return item._id.type === 'Virtual';
});
Here is an overview of how to do what you want dynamically:
var lhList = ['LH5', 'LH8']; // sourced from server ?
var lhSortedList = _.groupBy(servers, function(item) {
return item._id.idc;
});
$scope.lhData = {};
lhList.forEach(function(lhName) {
$scope.lhData[lhName + 'Virtual'] = _.filter(lhSortedList[lhName], function(item) {
return item._id.type === 'Virtual';
});
$scope.lhData[lhName + 'Physical'] = _.filter(lhSortedList[lhName], function(item) {
return item._id.type === 'Physical';
});
});

Categories