get specific value from json array inside of array - javascript

my first time trying to make an api request and get some data is not going to well.
I'm trying to find the "seed":"1" value and get the "franchise_id" value of "0010"
I haven't been successful even getting any of the seeds to console.log
Here is json
{
"version":"1.0",
"playoffBracket":{
"bracket_id":"1",
"playoffRound":[
{
"week":"14",
"playoffGame":[
{
"game_id":"1",
"away":{
"franchise_id":"0002",
"points":"137.2",
"seed":"3"
},
"home":{
"franchise_id":"0008",
"points":"111.7",
"seed":"6"
}
},
{
"game_id":"2",
"away":{
"franchise_id":"0006",
"points":"134.2",
"seed":"4"
},
"home":{
"franchise_id":"0011",
"points":"130.5",
"seed":"5"
}
}
]
},
{
"week":"15",
"playoffGame":[
{
"game_id":"3",
"away":{
"franchise_id":"0006",
"points":"153.3",
"winner_of_game":"2"
},
"home":{
"franchise_id":"0010",
"points":"162.8",
"seed":"1"
}
},
{
"game_id":"4",
"away":{
"franchise_id":"0002",
"points":"95.5",
"winner_of_game":"1"
},
"home":{
"franchise_id":"0012",
"points":"201.7",
"seed":"2"
}
}
]
}
]
},
"encoding":"utf-8"
}
i can log all the data , or some of the inner data , but haven't been able to do much else
$.ajax({
url: "apiurlhere",
success: function (data) {
console.log(data);
console.log(data.playoffBracket);
console.log(data.playoffBracket[0]);
}
});

That's because you are doing it wrong there is no playoffBracket[0] in your data. You need to do below:
data.playoffBracket.playoffRound[0]
To get franchise data you can use below:
data.playoffBracket.playoffRound[0].playoffGame[0].home
Or
data.playoffBracket.playoffRound[0].playoffGame[0].away
To get a single value
data.playoffBracket.playoffRound[0].playoffGame[0].home.franchise_id
Code to find the "seed":"1" value and get the "franchise_id" value of "0010"
// Method for searching
function findInJson(jsonData, findSeed, getFullObject = false) {
let ret = null;
for (let key in jsonData) {
for (let key2 in jsonData[key]) {
let awayHomeData = jsonData[key][key2];
if (Array.isArray(awayHomeData)) {
for (let key3 in awayHomeData) {
if (
awayHomeData[key3].hasOwnProperty("away") ||
awayHomeData[key3].hasOwnProperty("home")
) {
let homeOrAway = awayHomeData[key3];
let homeSeed = homeOrAway.home.seed;
let awaySeed = homeOrAway.away.seed;
if (findSeed == awaySeed) {
ret = homeOrAway.away;
} else if (findSeed == homeSeed) {
ret = homeOrAway.home;
}
}
}
}
}
}
if (ret !== null) {
ret = getFullObject ? ret : ret.franchise_id;
}
return ret;
}
// JSON Data
let data = {
version: "1.0",
playoffBracket: {
bracket_id: "1",
playoffRound: [
{
week: "14",
playoffGame: [
{
game_id: "1",
away: {
franchise_id: "0002",
points: "137.2",
seed: "3",
},
home: {
franchise_id: "0008",
points: "111.7",
seed: "6",
},
},
{
game_id: "2",
away: {
franchise_id: "0006",
points: "134.2",
seed: "4",
},
home: {
franchise_id: "0011",
points: "130.5",
seed: "5",
},
},
],
},
{
week: "15",
playoffGame: [
{
game_id: "3",
away: {
franchise_id: "0006",
points: "153.3",
winner_of_game: "2",
},
home: {
franchise_id: "0010",
points: "162.8",
seed: "1",
},
},
{
game_id: "4",
away: {
franchise_id: "0002",
points: "95.5",
winner_of_game: "1",
},
home: {
franchise_id: "0012",
points: "201.7",
seed: "2",
},
},
],
},
],
},
encoding: "utf-8",
};
// How to utilize the method
console.log(findInJson(data.playoffBracket.playoffRound, 22)); //will display null, because 22 doesn't exist
console.log(findInJson(data.playoffBracket.playoffRound, 2)); //will return 0012
console.log(findInJson(data.playoffBracket.playoffRound, 2, true)); //will return JSON object
The output looks as below:
null
"0012"
{
franchise_id: "0012",
points: "201.7",
seed: "2"
}
The solution can be seen on JSFiddle as well.

Related

Group array of objects with a specific key value pushed first

Given the following Array of Objects:
[
{
"teamFK": 8650,
"code": "yellow_cards",
"typeId": 554,
"value": "5",
"side": "home"
},
{
"teamFK": 8650,
"code": "goals",
"typeId": 554,
"value": "1",
"side": "home"
},
{
"teamFK": 8990,
"code": "yellow_cards",
"typeId": 555,
"value": "2",
"side": "away"
},
{
"teamFK": 8990,
"code": "goals",
"typeId": 555,
"value": "0",
"side": "away"
}
]
I would like to group this data by code and get this result:
{
"stats": [
{
"name": "yellow_cards",
"stats": ["5","2"]
},
{
"name": "goals",
"stats": ["2","0"]
}
]
}
What I've done is the following which works but I want to make sure that the alway the stat with "side":"home" always pushed first into the array "stats": []:
const groupedStats = Object.entries(
query.reduce((acc, { typeId, value, code, side }) => {
if (!acc[code]) {
acc[code] = [];
}
acc[code].push(value);
return acc;
}, {}),
).map(([name, stats]) => ({ name, stats }));
My approach is sort it first by side using Array.sort() and then looping through the objects and adding it to stats
i created a const match to find if there is a match already so i dont have to add the name and value again basically if its not a match i'll add it to the stats array and if its a match then i'll just update the current index
const objs = [
{
teamFK: 8650,
code: "yellow_cards",
typeId: 554,
value: "5",
side: "home",
},
{
teamFK: 8650,
code: "goals",
typeId: 554,
value: "1",
side: "away",
},
{
teamFK: 8990,
code: "yellow_cards",
typeId: 555,
value: "2",
side: "away",
},
{
teamFK: 8990,
code: "goals",
typeId: 555,
value: "0",
side: "home",
},
];
let stats = [];
const transformedObj = objs
.sort((a, b) => {
if (a.side > b.side) {
return -1;
}
if (a.side < b.side) {
return 1;
}
return 0;
})
.forEach((obj) => {
const match = stats.find((stat) => stat.name === obj.code);
const statsIndex = stats.findIndex((stat) => stat.name === obj.code);
if (!match) {
stats = [...stats, { name: obj.code, value: [obj.value] }];
} else {
stats[statsIndex] = {
name: stats[statsIndex].name,
value: [...stats[statsIndex].value, obj.value],
};
}
});
console.log(stats);
You can sort array and use key grouping approach:
const data = [{"teamFK": 8650,"code": "yellow_cards","typeId": 554,"value": "5","side": "home"},{"teamFK": 8650,"code": "goals","typeId": 554,"value": "1","side": "home"},{"teamFK": 8990,"code": "yellow_cards","typeId": 555,"value": "2","side": "away"},{"teamFK": 8990,"code": "goals","typeId": 555,"value": "0","side": "away"}];
const groups = data
.sort(({ side: a }, { side: b }) => b.localeCompare(a))
.reduce((acc, { code, value }) => {
acc[code] ??= { name: code, stats: [] };
acc[code]['stats'].push(value);
return acc;
}, {});
const result = { stats: Object.values(groups) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }

How to calculate average from nested values in an array using javascript?

I am trying to calculate the average duration for each stage. So in the array below - I should be able to get the average duration for 'test1', which would be 2.
jobs = [
{
"build_id": 1,
"stage_executions": [
{
"name": "test1"
"duration": 1,
},
{
"name": "test2"
"duration": 16408,
},
{
"name": "test3"
"duration": 16408,
},
]
},
{
"build_id": 2,
"stage_executions": [
{
"name": "test1"
"duration": 3,
},
{
"name": "test2"
"duration": 11408,
},
{
"name": "test3"
"duration": 2408,
},
]
}
]
My failed attempt:
avgDuration: function(jobs) {
let durationSum = 0
for (let item = 0; item < this.jobs.length; item++) {
for (let i = 0; i < this.jobs[item].stage.length; item++) {
durationSum += stage.duration
}
durationAverage = durationSum/this.jobs[item].stage.length
}
return durationAverage
What am I doing wrong? I'm not sure how to accomplish this since the duration is spread out between each job.
UPDATE:
This is return a single average for all stages rateher than per stage
<template>
<div class="stages">
<h3>
Average Duration
</h3>
<table>
<tbody>
<tr v-for="item in durations">
<td>
<b>{{ item.average}} {{ item.count }}</b>
// this returns only 1 average and 177 count instead of 10
<br />
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { calculateDuration } from "../../helpers/time.js";
import { liveDuration } from "../../helpers/time.js";
import moment from "moment";
export default {
name: "Stages",
data() {
return {
jobs: [],
durations: []
};
},
methods: {
avgDuration: function(jobs) {
var averageByName = {}; // looks like { 'name': { average: 111, count: 0 }}
for (var job of jobs) {
for(var stage of job.stage_execution) {
if (averageByName[stage.name] == null) { // we need a new object
averageByName[stage.name] = { average: 0, count: 0 };
}
// just name it so its easier to read
var averageObj = averageByName[stage.name];
// update count
averageObj.count += 1;
// Cumulative moving average
averageObj.average = averageObj.average + ( (stage.duration - averageObj.average) / averageObj.count );
console.log(averageObj.count)
}
}
return averageByName
},
},
created() {
this.JobExecEndpoint =
process.env.VUE_APP_TEST_URL +
"/api/v2/jobs/?limit=10";
fetch(this.JobExecEndpoint)
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.length; i++) {
this.jobs.push({
name: body[i].job.name,
job: body[i].job,
stage_execution: body[i].stage_executions,
});
}
})
.then(() => {
this.$emit("loading", true);
})
.then(() => {
this.durations = this.avgDuration(this.jobs);
})
.catch(err => {
console.log("Error Fetching:", this.JobExecEndpoint, err);
return { failure: this.JobExecEndpoint, reason: err };
});
}
};
</script>
We can do this pretty simply and without overflow from having too many numbers by using a Cumulative moving average and a few loops.
Here is a line the relevant Wikipedia page on Moving Averages and the most relvant formula below.
I will not go into much detail with the above as there are a lot of documents describing this sort of thing. I will however say that the main reason to this over adding all the values together is that there is a far lower chance of overflow and that is why I am using it for this example.
Here is my solution with comments made in code.
var jobs = [ { "build_id": 1, "stage_executions": [ { "name": "test1", "duration": 1, }, { "name": "test2", "duration": 16408, }, { "name": "test3", "duration": 16408, }, ] }, { "build_id": 2, "stage_executions": [ { "name": "test1", "duration": 3, }, { "name": "test2", "duration": 11408, }, { "name": "test3", "duration": 2408, }, ] } ];
var averageByName = {}; // looks like { 'name': { average: 111, count: 0 }}
for (var job of jobs) {
for(var stage of job.stage_executions) {
if (averageByName[stage.name] == null) { // we need a new object
averageByName[stage.name] = { average: 0, count: 0 };
}
// just name it so its easier to read
var averageObj = averageByName[stage.name];
// update count
averageObj.count += 1;
// Cumulative moving average
averageObj.average = averageObj.average + ( (stage.duration - averageObj.average) / averageObj.count );
}
}
// print the averages
for(var name in averageByName) {
console.log(name, averageByName[name].average);
}
Let me know if you have any questions or if anything is unclear.
You could collect the values in an object for each index and map later only the averages.
var jobs = [{ build_id: 1, stage_executions: [{ name: "test1", duration: 1 }, { name: "test2", duration: 16408 }, { name: "test3", duration: 16408 }] }, { build_id: 2, stage_executions: [{ name: "test1", duration: 3 }, { name: "test2", duration: 11408 }, { name: "test3", duration: 2408 }] }],
averages = jobs
.reduce((r, { stage_executions }) => {
stage_executions.forEach(({ duration }, i) => {
r[i] = r[i] || { sum: 0, count: 0 };
r[i].sum += duration;
r[i].avg = r[i].sum / ++r[i].count;
});
return r;
}, []);
console.log(averages.map(({ avg }) => avg));
console.log(averages);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I've used Array.prototype.flatMap to flatten the jobs array into an array of {name:string,duration:number} object. Also, to make more solution a bit more dynamic the function takes in a field argument which returns the average for that specific field.
const jobs = [
{
"build_id": 1,
"stage_executions": [
{
"name": "test1",
"duration": 1,
},
{
"name": "test2",
"duration": 16408,
},
{
"name": "test3",
"duration": 16408,
},
]
},
{
"build_id": 2,
"stage_executions": [
{
"name": "test1",
"duration": 3,
},
{
"name": "test2",
"duration": 11408,
},
{
"name": "test3",
"duration": 2408,
},
]
}
];
const caller = function(jobs, field) {
const filtered = jobs
.flatMap((item) => item.stage_executions)
.filter(item => {
return item.name === field;
})
const total = filtered.reduce((prev, curr) => {
return prev + curr.duration;
}, 0)
return total / filtered.length;
}
console.log(caller(jobs, 'test1'))
console.log(caller(jobs, 'test2'))
console.log(caller(jobs, 'test3'))
In case you get the error flatMap is not a function. You can add this code snippet in your polyfill or at the top of your js file.
Array.prototype.flatMap = function(lambda) {
return Array.prototype.concat.apply([], this.map(lambda));
};
PS: for demostration, I obtained the flatMap implementation from here

How to find max value in nested array nd update it?

the document is:
var house = {
"name": name1,
"steps":[
{
"step_id":1,
"value":9999,
"members": [
{
"user_id": 7,
},
{
"user_id": 1
}
]
},
{
"step_id":6,
"value":9999,
"members": [
{
"user_id": 7,
},
{
"user_id": 1
}
]
}
}
}
All I need to do is to find document by name, find there step with MAX "step_id" value and update there "value" field to -1;
I tried this but it doesnt' work for me. will be glad for any help :)
db.collection('houses').find( {"name": "name1",max: { $max : "steps.$step_id" },{$set:{"steps.$.value":-1}, function (err, doc) {
if(err){
return
}
console.log('doc updated');
} );
You could use Array#reduce and return for every loop the object with the greater step_id.
Then update the property.
var house = { name: "name1", steps:[{ step_id: 1, value: 9999, "members": [{ user_id: 7 }, { user_id: 1 }] }, { step_id: 6, value: 9999, members: [{ user_id: 7 }, { user_id: 1 }] }] };
house.steps.reduce((a, b) => a.step_id > b.step_id ? a : b).value = -1;
console.log(house);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Is there a "good" way to get normalised rows of data from ES nested aggregations?

Elasticsearch nested aggregations allow you to effectively group by multiple fields. But what it returns is buckets which are nested for each field you group by.
What I need is an array of objects for each group combination.
My query:
{
index : 'stats',
type : 'click',
size : 0,
body : {
aggs : {
publisher : {
terms : {
field : 'publisherData.id'
},
aggs : {
advertiser : {
terms : {
field : 'advertiserData.id'
},
aggs : {
country : {
terms : {
field : 'request.location.country.iso_code'
},
aggs : {
revenue : {
sum : {
field : 'revenueData.data.USD'
}
},
cost : {
sum : {
field : 'costData.data.USD'
}
}
}
}
}
}
}
}
}
}
}
The result, limited to one entry per field. Normally there would be more so all combinations of nested fields would have to be mapped to an array for display in a table.
{
"took": 562,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 4812178,
"max_score": 0,
"hits": []
},
"aggregations": {
"publisher": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 3114671,
"buckets": [
{
"key": 4,
"doc_count": 1697507,
"advertiser": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 555390,
"buckets": [
{
"key": 5,
"doc_count": 1142117,
"country": {
"doc_count_error_upper_bound": 13807,
"sum_other_doc_count": 544585,
"buckets": [
{
"key": "us",
"doc_count": 424137,
"revenue": {
"value": 772282
},
"cost": {
"value": 53698.84903321415
}
}
]
}
}
]
}
}
]
}
}
}
What I need (normally there would be multiple objects here, one for each combination of nested fields) :
[{
publisher:4,
advertiser:5,
country:'us',
cost:53698.84903321415,
revenue:772282
}]
What's the best way to get this result from the above nested structure or even better and if possible, from elasticsearch itself.
Any help greatly appreciated.
In plain Javascript, you could use an iterative and recursive approach - but I suggest to use some feature of ES for getting the wanted result.
function getValues(object) {
function iter(o, p) {
var add = false;
Object.keys(o).forEach(function (k) {
if (['key', 'doc_count'].indexOf(k) !== -1) {
return;
}
if (Array.isArray(o[k].buckets)) {
o[k].buckets.forEach(function (a) {
iter(a, p.concat([[k, a.key]]));
});
return;
}
add = true;
p.push([k, o[k].value]);
});
add && result.push(Object.assign({}, ...p.map(a => ({[a[0]]: a[1]}))));
}
var result = [];
iter(object.aggregations, []);
return result;
}
var data = { took: 562, timed_out: false, _shards: { total: 5, successful: 5, failed: 0 }, hits: { total: 4812178, max_score: 0, hits: [] }, aggregations: { publisher: { doc_count_error_upper_bound: 0, sum_other_doc_count: 3114671, buckets: [{ key: 4, doc_count: 1697507, advertiser: { doc_count_error_upper_bound: 0, sum_other_doc_count: 555390, buckets: [{ key: 5, doc_count: 1142117, country: { doc_count_error_upper_bound: 13807, sum_other_doc_count: 544585, buckets: [{ key: "us", doc_count: 424137, revenue: { value: 772282 }, cost: { value: 53698.84903321415 } }] } }] } }] } } };
console.log(getValues(data));

combine json array into one json array by id

I want to merge item and purchases array of json into one by matching their property value.
Here's the source :
{
"item": [
{
"invoiceId": 1
},
{
"invoiceId": 2
},
{
"invoiceId": 3
}
],
"purchase": [
{
"id": "1",
"date": "12/1/2014"
},
{
"id": "2",
"date": "12/1/2014"
},
{
"id": "3",
"date": "12/1/2014"
}
]
}
I want to produce something like this :
{
"combined": [
{
"invoiceId": 1,
"id": "1",
"date": "12/1/2014"
},
{
"invoiceId": 2,
"id": "2",
"date": "12/1/2014"
},
{
"invoiceId": 3,
"id": "3",
"date": "12/1/2014"
}
]
}
How can I match the item.invoiceId with purchase.id?
Solution
assuming obj is your object
var new_obj = {combined:[]};
obj["purchase"].forEach(function(a) {
obj["item"].forEach(function(b){
if (+b["invoiceId"]===(+a["id"])) {
a["invoiceId"] = b["invoiceId"] || 0;//WILL MAKE INVOICEID 0 IF IT IS NOT DEFINE. CHANGE 0 TO YOUR NEEDS
new_obj.combined.push(a);
}
});
});
How it works
The first .forEach() loops through obj.purchase. Then we loop through obj.item To check if their is a matching invoiceId (if you don't need to make sure their is a matching invoiceId, use the alternate code). Then, we simply add a new value to the new_obj
The result (copied from console) is:
{
"combined":[
{
"id":"1",
"date":"12/1/2014",
"invoiceId":1
},
{
"id":"2",
"date":"12/1/2014",
"invoiceId":2
},
{
"id":"3",
"date":"12/1/2014",
"invoiceId":3
}
]
}
Alternative Code
Use this if you don't need to make sure, invoiceId is there
var new_obj = {combined:[]};
obj["purchase"].forEach(function(a){a["invoiceId"]=a["id"];new_obj.combined.push(a);});
One way of achieving what you want will be
var result = {};
var getMatchingPurchase = function(invoiceId) {
return data.purchase.filter(function(purchase) {
return invoiceId == purchase.id;
})[0];
};
result.combined = data.item.map(function(invoice) {
var purchase = getMatchingPurchase(invoice.invoiceId);
return {
invoiceId: invoice.invoiceId,
id: purchase.id,
date: purchase.date
};
});
console.log(result);
It will print like bellow
{ combined:
[ { invoiceId: 1, id: '1', date: '12/1/2014' },
{ invoiceId: 2, id: '2', date: '12/1/2014' },
{ invoiceId: 3, id: '3', date: '12/1/2014' } ] }
Note:- I'm using map and filter functions which are not supported in IE8. If you want to use in IE8 you have to use for loop.
If you have to support old browsers like IE8 (poor guy...), note that the native forEach might not be supported, in this case you can use lodash for cross-browser compatibility:
function getCombinedResult(source){
var combinedList = [];
_.each(source.item, function(item){
_.each(source.purchase, function(purchase){
if (item['invoiceId'].toString() != purchase['id'].toString()) return;
var combinedItem = _.extend(item, purchase)
combinedList.push(combinedItem);
});
})
return {"combined": combinedList};
}

Categories