Javascript nested array transformation - javascript

I'm trying to get my head around Javascript array functions. I have a nested array like this, where every row covers the same time period:
[{
"category": "fruit",
"variety": "apple",
"data": [{
"day": 1,
"units": 2
}, {"day": 2,
"units": 4
}]
},{
"category": "fruit",
"variety": "orange",
"data": [{
"day": 1,
"units": 3
}, {"day": 2,
"units": 5
}]
},{
"category": "veg",
"variety": "tomato",
"data": [{
"day": 1,
"units": 4
}, {"day": 2,
"units": 2
}]
}]
I would like to sum the units by day by category, to get an array like this:
[{
"category": "fruit",
"data": [{
"day": 1,
"units": 5
}, {"day": 2,
"units": 9
}]
},{
"category": "veg",
"data": [{
"day": 1,
"units": 4
}, {"day": 2,
"units": 2
}]
}]
I've been tackling this through long looping if statements, and making a bit of a hash of it. Can you see an elegant way to solve this?
Many thanks!

The solution is pretty obvious: Loop through the array, and store the data in a key-value pair. Then, loop through the has, and construct the resulting array using Array.prototype.map. Finally, if you want a nicely formatted JSON-string, use JSON.stringify(result, null, 4);, where 4 is the number of spaced for pretty formatting.
Demo: http://jsfiddle.net/jde6S/
var list = [ ... ];
var hash = {};
for (var i=0; i<list.length; i++) {
var obj = list[i];
// This part makes sure that hash looks like {fruit:[], veg: []}
var hashObjCat = hash[obj.category];
if (!hashObjCat) {
hashObjCat = hash[obj.category] = {};
}
// This part populates the hash hashObjCat with day-unit pairs
for (var j=0; j<obj.data.length; j++) {
var data = obj.data[j];
if (hashObjCat[data.day]) hashObjCat[data.day] += data.units;
else hashObjCat[data.day] = data.units;
}
}
// Now, we hash looks like {fruit: {1:5, 2:9} }
// Construct desired object
var result = Object.keys(hash).map(function(category) {
// Initial object
var obj = {category: category, data:[]};
var dayData = Object.keys(hash[category]);
// This part adds day+units dicts to the data array
for (var i=0; i<dayData.length; i++) {
var day = dayData[i];
var units = hash[category][day];
obj.data.push({day: day, units: units});
}
return obj;
});
// Test:
console.log(JSON.stringify(result, null, 4));

reduce the array to an object (See #RobW's answer on how to do that with loops):
var data = [...] // your input
// Iterate the data with reduce...
var sumsbycategory = data.reduce(function(map, fruit) {
var cat = fruit.category;
// set an property to an object, iterating the days array...
map[cat] = fruit.data.reduce(function(sums, day) {
var d = day.day;
// set or update the units for this day
sums[d] = (sums[d] || 0) + day.units;
return sums; // into the next iteration
}, map[cat] || {}) // ...passing in the already existing map for this cat or a new one
return map; // into the next iteration
}, {}); // ...passing in an empty object
Now we have the following format:
{"fruit":{"1":5,"2":9},"veg":{"1":4,"2":2}}
...which I think is much easier to handle, but lets build your array:
var result = []; // init array
for (var cat in sumsbycategory) { // loop over categories
var data = []; // init array
// add category object:
result.push({category:cat, data:data});
for (var day in sumsbycategory[cat]) // loop over days in category
// add day object
data.push({day:day, units:sumsbycategory[cat][day]});
}
But, wait! An object has no order, and it could happen that day2 comes before days1 in the result array (which might break your appplication?) So, you could use map on the keys of that object which also can be sorted before, to generate the array in one clean-looking expression:
var result = Object.keys(sumsbycategory).map(function(cat) {
return {
category: cat,
data: Object.keys(sumsbycategory[cat])
.sort(function numbercompare(a,b){ return a-b; })
.map(function(day) {
return {
day: day,
units: sumsbycategory[cat][day]
};
})
};
});
result:
[{
"category": "fruit",
"data": [{"day":"1","units":5},{"day":"2","units":9}]
},{
"category": "veg",
"data": [{"day":"1","units":4},{"day":"2","units":2}]
}]
(Demo at jsfiddle.net)

If you're willing to grab some external code and use it to essentially re-index your structure you could probably do something. I know the old dojo data api was a mess to work with, but could allow something like what you seem to be asking.
Personally I'd stick with loops, just keep your variable names readable. Also remember the object literals can be addressed as either an array/hash syntax x[y] or dot syntax x.y

Related

JS dynamically access & delete nested object property [duplicate]

This is the sample json:
{
"search": {
"facets": {
"author": [
],
"language": [
{
"value": "nep",
"count": 3
},
{
"value": "urd",
"count": 1
}
],
"source": [
{
"value": "West Bengal State Council of Vocational Education & Training",
"count": 175
}
],
"type": [
{
"value": "text",
"count": 175
}
],
}
}
There are several ways to delete key search.facets.source:
delete search.facets.source
delete jsobObj['search']['facets']['source']
var jsonKey = 'source';
JSON.parse(angular.toJson(jsonObj), function (key, value) {
if (key != jsonKey)
return value;
});
Above 1 & 2 are not dynamic, and 3 is one of the way but not a proper way. Because if source is present in another node then it will not work. Please anybody can tell me how to delete it dynamically in any kind of nested key. Because we can not generate sequence of array dynamically in above 2.
Assuming you're starting from this:
let path = 'search.facets.source';
Then the logic is simple: find the search.facets object, then delete obj['source'] on it.
Step one, divide the path into the initial path and trailing property name:
let keys = path.split('.');
let prop = keys.pop();
Find the facets object in your object:
let parent = keys.reduce((obj, key) => obj[key], jsonObj);
Delete the property:
delete parent[prop];
I have found out another solution, it is very easy.
var jsonKey = 'search.facets.source';
eval('delete jsonObj.' + jsonKey + ';');

How to determine the value in array which is in an object?

I get 100 objects (JSON) from a website, which is build like this.
"object" : [{
"id": 12243,
"name": 'Carl',
"text": 'subheader',
"tags": ["cars","child", "blue"],
...
},
{
"id": 12245,
"name": 'Dark',
"text": 'subheader',
"tags": ["cars"],
...
}
...
]
I want to get only which has the tag child. How can I do it?
You could try something like this:
var objectsWithChild = json.object.filter(function(o){
return o.tags.includes("child");
});
where json is your json string.
Using Array.prototype.filter()
var objs = [{
"id": 12243,
"name": 'Carl',
"text": 'subheader',
"tags": ["cars","child", "blue"]
},
{
"id": 12245,
"name": 'Dark',
"text": 'subheader',
"tags": ["cars"]
}]
var objsWithTags = objs.filter(obj => obj.tags.indexOf('child') > -1);
console.log(objsWithTags);
Christos has a cleaner way to do it, but this way might be clearer to a beginner. One way to do it would be to use nested for loops.
var myObjects = []
for (var i = 0; i < data.object.length; i++) {
var ob = data.object[i];
for (var j = 0; j < ob.tags.length; j++) {
if (ob.tags[i] === "child") {
myObjects.push(ob);
}
}
}
There are probably multiple ways to implement this.
But the main ideas are mostly the same.
Here in textual form:
Define a container (an array for example) for your matched objects.
Loop through all objects.
Loop through all entries of your array "tags".
Check if you find your desired value "child".
Remember your found object by adding it to your container (array).

replace multiple values in json/jsObject/string

I have a response from a web service and want to replace some values in the response with my custom values.
One way is to write a tree traverser and then check for the value and replace with my custom value
so the response is some what like this:
[
{
"name": "n1",
"value": "v1",
"children": [
{
"name": "n2",
"value": "v2"
}
]
},
{
"name": "n3",
"value": "v3"
}
]
now my custom map is like this
const map = {
"v1": "v11",
"v2": "v22",
"v3": "v33"
};
All I want is
[
{
"name": "n1",
"value": "v11",
"children": [
{
"name": "n2",
"value": "v22"
}
]
},
{
"name": "n3",
"value": "v33"
}
]
I was thinking if I could stringify my response and then replace values using a custom build regex from my map of values.
Will it be faster as compared to tree traverser?
If yes, how should I do that?
somewhat like this
originalString.replace(regexp, function (replacement))
The tree traversal is faster
Note that some things could be done more efficiently in the regex implementation but I still think there are some more bottlenecks to explain.
Why the regex is slow:
There are probably many more reasons why the regex is slower but I'll explain at least one significant reason:
When you're using regex to find and replace, you're using creating new strings every time and performing your matches every time. Regex expressions can be very expensive and my implementation isn't particularly cheap.
Why is the tree traversal faster:
In the tree traversal, I'm mutating the object directly. This doesn't require creating new string objects or any new objects at all. We're also not performing a full search on the whole string every time as well.
RESULTS
run the performance test below. The test using console.time to record how long it takes. See the the tree traversal is much faster.
function usingRegex(obj, map) {
return JSON.parse(Object.keys(map).map(oldValue => ({
oldValue,
newValue: map[oldValue]
})).reduce((json, {
oldValue,
newValue
}) => {
return json.replace(
new RegExp(`"value":"(${oldValue})"`),
() => `"value":"${newValue}"`
);
}, JSON.stringify(obj)));
}
function usingTree(obj, map) {
function traverse(children) {
for (let item of children) {
if (item && item.value) {
// get a value from a JS object is O(1)!
item.value = map[item.value];
}
if (item && item.children) {
traverse(item.children)
}
}
}
traverse(obj);
return obj; // mutates
}
const obj = JSON.parse(`[
{
"name": "n1",
"value": "v1",
"children": [
{
"name": "n2",
"value": "v2"
}
]
},
{
"name": "n3",
"value": "v3"
}
]`);
const map = {
"v1": "v11",
"v2": "v22",
"v3": "v33"
};
// show that each function is working first
console.log('== TEST THE FUNCTIONS ==');
console.log('usingRegex', usingRegex(obj, map));
console.log('usingTree', usingTree(obj, map));
const iterations = 10000; // ten thousand
console.log('== DO 10000 ITERATIONS ==');
console.time('regex implementation');
for (let i = 0; i < iterations; i += 1) {
usingRegex(obj, map);
}
console.timeEnd('regex implementation');
console.time('tree implementation');
for (let i = 0; i < iterations; i += 1) {
usingTree(obj, map);
}
console.timeEnd('tree implementation');
Will it be faster as compared to tree traverser?
I don't know. I think it would depend on the size of the input, and the size of the replacement map. You could run some tests at JSPerf.com.
If yes, how should I do that?
It's fairly easy to do with a regex-based string replacement if the values you are replacing don't need any special escaping or whatever. Something like this:
const input = [
{
"name": "n1",
"value": "v1",
"children": [
{
"name": "n2",
"value": "v2"
}
]
},
{
"name": "n3",
"value": "v3"
}
];
const map = {
"v1": "v11",
"v2": "v22",
"v3": "v33"
};
// create a regex that matches any of the map keys, adding ':' and quotes
// to be sure to match whole property values and not property names
const regex = new RegExp(':\\s*"(' + Object.keys(map).join('|') + ')"', 'g');
// NOTE: if you've received this data as JSON then do the replacement
// *before* parsing it, don't parse it then restringify it then reparse it.
const json = JSON.stringify(input);
const result = JSON.parse(
json.replace(regex, function(m, key) { return ': "' + map[key] + '"'; })
);
console.log(result);
definitely traverser go faster as string replace means travels against each characters in the final string as opposed to iterator that can skips no necessarily item.

Populate a variable using jQuery based on selections in a drop down

I'm trying to build a nested array in jQuery based on a user's selection from a drop down menu. This will be used in a JSON request at a later date.
So far my code does produce (almost) the required result, however no matter order i select the options from my drop down menu, the output (which i log in the console at the end) is always the same.
$('#comboGenre').change(function () {
var values = $('#comboGenre').val();
var parsedJSON = JSON.parse($data); //Data returned from ajax request
for (var i = 0; i < values.length; i += 1) {
$genreList = parsedJSON.genre[i];
console.log($genreList);
}
});
So if i select RPG and Action from my drop down, the output gives me RPG and Driving. If i selected RPG, Driving and Action (in that order), i get what i would expect RPG, Driving and Action.
So it's just iterating through my JSON, when really it should be returning the 'selected' option.
How can i achieve this?
My JSON looks like this if it's useful:
{"genres": [{
"genre": "RPG",
"publishers": [{
"publisher": "Square",
"games": [{
"game": "FFX",
"rating": [
12, 15
]
}]
}]
},
{
"genre": "Driving",
"publishers": [{
"publisher": "Turn10",
"games": [{
"game": "Forza",
"rating": [
5
]
}]
}]
},
{
"genre": "Action",
"publishers": [{
"publisher": "EA",
"games": [{
"game": "COD",
"rating": [
18, 20
]
}]
}]
}
]}
EDIT:
I've also tried this:
$('#comboGenre').change(function () {
var parsedJSON = JSON.parse($data);
$genreList = "";
$.each(parsedJSON.genres, function(index, value){
$genreList = parsedJSON.genres[index];
console.log($genreList);
});
});
And i end up getting ALL the objects in my JSON, so from here, i'm only wanting to add the selected object to the $genreList variable.
If you broke out some of the logic and created a genre finding function and used the selected string to find the proper object you could then put the object into the variable you will use later. I do some checking to ensure that the genre that has been selected isn't already in my array which is because I am using the multiple select
JSFiddle: http://jsfiddle.net/vkTFq/
Code:
$(function(){
var selectedGenres = [];
var genres =[{"genre":"RPG","publishers":[{"publisher":"Square","games":[{"game":"FFX","rating":[12,15]}]}]},{"genre":"Driving","publishers":[{"publisher":"Turn10","games":[{"game":"Forza","rating":[5]}]}]},{"genre":"Action","publishers":[{"publisher":"EA","games":[{"game":"COD","rating":[18,20]}]}]}]
$('#comboGenre').change(function() {
$(this).find(":selected").each(function() {
var selectedGenre = findGenre($(this).val())
if (!genreAlreadySelected(selectedGenre.genre)) {
selectedGenres.push(selectedGenre);
};
});
console.log (JSON.stringify(selectedGenres));
});
function genreAlreadySelected(genre){
for(var i = 0; i < selectedGenres.length; i++){
if (genre == selectedGenres[i].genre) {
return true;
};
return false;
}
}
function findGenre(genre){
for(var i = 0; i < genres.length; i ++){
console.log(genre)
if(genre == genres[i].genre){
return genres[i];
}
}
};
});

JSON/JS Object accessing key when you don't know the name in Google Apps Script

I'm working with a response from the Webtrends API in Google apps script and I have a JSON/JS object that looks like this:
"data": [
{
"period": "Month",
"start_date": "2013-12",
"end_date": "2013-12",
"attributes": {},
"measures": {
"Visits": 500
},
"SubRows": [
{
"facebook.com": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"google.co.uk": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"newsnow.co.uk": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"No Referrer": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
},
"t.co": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
}
}
]
}
]
What I need to access is the names i.e facebook.com etc... and visit numbers for each of the SubRows.
I'm able to get the visit numbers, but I can't work out how to get the names. Please note the names will change constantly as different sites will send different amounts of traffic each day.
Section of my code at the moment where I get the visit numbers:
for(i in dObj){
var data = dObj[i].SubRows;
var sd = dObj[i].start_date;
var ed = dObj[i].end_date;
if(sd == ed){
var timep = ""+ sd;
}
else{
var timep = ""+ sd + "-" + ed;
}
var subRows = data[0];
Logger.log(subRows);
for(i in subRows){
var row = subRows[i];
var rmeasures = row.measures;
var rvis = rmeasures.Visits;
values = [timep,"",rvis]; //Blank string for where the name of the site would go
}
}
I've tried the following links, but none of them seem to have the answer:
Getting JavaScript object key list
How to access object using dynamic key?
How to access key itself using javascript
How do I access properties of a javascript object if I don't know the names?
I'm just using vanilla google apps script as I don't have any experience with Jquery etc...
Any help would be much appreciated!
I usually use a little helper function that looks like this:
var keyVal = function(o) {
var key = Object.keys(o)[0];
return {"key": key, "val":o[key]};
} ;
This will map an object with a variable key to a key/value object {key:...., val:{}}, which is usually convenient enough to work with.
describe.only ("stack overflow answer", function(){
it ("is should create a key/value pair" , function(){
var res = keyVal( {
"facebook.com": {
"attributes": {},
"measures": {
"Visits": 100
},
"SubRows": null
}});
res.key.should.equal('facebook.com');
res.val.attributes.should.deep.equal({});
});
Within the loop, the variable i contains the current key. Replacing the empty string with i should give you what you need.
You might also want to look at some of the more functional tools built into Javascript. Some more concise code might also be more explicit:
data.map(function(datum) {
var timep = datum.start_date == datum.end_date ? datum.end_date :
(data.start_date + "-" + datum.end_date);
return datum.SubRows.map(function(subRow) {
return Object.keys(subRow).map(function(key) {
return [timep, key, subRow[key].measures.Visits];
});
});
});
would return an object something like this:
[
[
[
["2013-12", "facebook.com", 100],
["2013-12", "google.co.uk", 100],
["2013-12", "newsnow.co.uk", 100],
["2013-12", "No Referrer", 100],
["2013-12", "t.co", 100 ]
]
]
]
This just uses map and Object.keys to simplify some of what you're doing with explicit loops.

Categories

Resources