Converting JSON from API to collection of "course" objects - javascript

I have an end goal of making an application that allows users to put in parameters for their class schedule (i.e "I need Class A, Class B, and Class C, I'd like to take either Class D, G, E, I only want to take 4 classes, and I don't want class before 10am) and then I'd like to present to the user all of their possible schedules (using something like events in Full Calendar http://arshaw.com/fullcalendar/)
I don't know much about working with APIs or JSON (new to JavaScript also) so I'm just a bit overwhelmed with the possibilities on how to do this.
I've searched extensively and found $.getJSON mentioned a lot, so I have something like this:
$.getJSON(
"http://vazzak2.ci.northwestern.edu/courses/?term=4540&subject=ACCT",
function(result)
{
}
);
which I believe returns the JSON as a string, yes? Is there a good way to split this into objects or maybe arrays?
Part of the JSON is shown here for brevity:
[
{
"id": 52239,
"title": "Accounting for Decision Making",
"term": "2014 Spring",
"school": "KGSM",
"instructor": {
"name": "Ronald A Dye",
"bio": null,
"address": null,
"phone": null,
"office_hours": null
},
"subject": "ACCT",
"catalog_num": "430-0",
"section": "71",
"room": "Wieboldt Hall 207",
"meeting_days": "Tu",
"start_time": "18:00:00",
"end_time": "21:00:00",
"start_date": "2014-03-31",
"end_date": "2014-06-07",
"seats": 65,
"overview": null,
"topic": null,
"attributes": null,
"requirements": null,
"component": "LEC",
"class_num": 37561,
"course_id": 3,
"coursedesc_set": [],
"coursecomponent_set": []
},
...
...
]
I have tried:
obj = JSON.parse(result);
and suggestions on the web say to follow with something like:
alert(obj.id);
However, this doesn't work, because is seems like the JSON pulled from the API is nested in a way (ie it's all the courses offered in that subject, and each is a JSON). So it doesn't know which "id" value to return. If you look at the link to the JSON you may get a better idea of what I mean. If anyone has any guidance on where to start here it'd be much appreciated. I've just been reading about json and still haven't gotten anywhere.
If I could figure out how to parse the json into each of it's objects, then I could call "all classes with 'time' > 10am for example.

The returned object is an array. You'll need to iterate over each element in the array.
Updated with a working demo:
$.getJSON("http://vazzak2.ci.northwestern.edu/courses/?term=4540&subject=ACCT", function(result) {
var earlyCourses = [];
$(result).each(function (index, item) {
$('#search_results').text((index+1) + " total courses");
if (item.start_time > '10:00:00') {
console.log(item);
$('#morning_results_list').append('<li>' + item.title + '</li>');
}
});
});
With the following boilerplate container:
<h2 id="search_results"></h2>
<div id="morning_results_container">
<h5>Morning Courses</h5>
<ul id="morning_results_list">
</ul>
</div>
Some notes about this example:
The time check here is horribly naive and is reverting to an alphabetical comparison rather than an actual date/time check.
Inserting multiple li in this fashion is bad for UI performance, all updates to the DOM should be batched to a single update. The example of per iteration insertion will work as long as the number of results is small (less than several hundred).
JSFIDDLE EXAMPLE: http://jsfiddle.net/h2a3t/

Related

How to access an array of objects with tooltip.format() from anychart.js

I am having trouble trying to present an array of objects on the tooltip of an Anychart.js map. I understand that we can access the dataset by doing something like: %[name of property in data set]. My data set has the following form:
{
"country": "Austria",
"id": "AT",
"continent": "Europe",
"songs": [
{
"rank": 33,
"title": "Stuck with U (with Justin Bieber)",
"artists": "Ariana Grande, Justin Bieber",
"album": "Stuck with U",
"explicit": 0,
"duration": "3:48"},
{
"rank": 34,
"title": "Late Night",
"artists": "Luciano",
"album": "Late Night",
"explicit": 0,
"duration": "3:20"
},
... more objects
]
}
}
If I wanted to access the Country property I would simply add it to the tooltip by doing:
tooltip.format("Country: " + {%country});
The issue is when trying to access an array of objects, I have tried different variations and none of them worked. Trying to show the title of every song:
tooltip.format({%songs}.{%title});
tooltip.format({%songs.%title});
tooltip.format({%songs}[{%title}]);
I also saw in the documentation that we can send a function as argument so I tried the following where I would concatenate every title of the collection but did not succeed either:
tooltip.format(function() {
let concatenated = '';
this.songs.forEach(song => {
concatenated += song + ' ';
});
return concatenated;
});
I would really appreciate your help guys.
String tokens do not support nested objects/properties. But you can use the callback function of the formatted to get access to songs. The context prototype includes getData() method provides that. Like this:
series.tooltip().format(function() {
console.log(this.getData('songs'));
return 'tooltip';
});
For details, check the live sample we prepared.
In case any one else is looking for a solution to this answer. I figured out how to loop through an embed array, and call on specific information.
chart.edges().tooltip().format(function () {
var format = ''
var songs = this.getData('songs');
songs.forEach(function (data, builtin, dom) {
format = '<p>'+data['title']+' by '+data['artists']+' </span></p>' + format
});
console.log(format)
return format
});

Code by Zapier to loop through an array of objects, pulling out one value per object and then average those values

Using JavaScript for Zapier, I am trying to calculate the average value of a property from an array of object.
Here is one of the objects...
{
"code": 0,
"data": [
{
"id": "28737",
"owner": "1",
"date": "1581945706",
"dla": "0",
"dlm": "1582551517",
"system_source": "3",
"source_location": null,
"ip_addy": null,
"ip_addy_display": null,
"import_id": "0",
"contact_cat": "*/*",
"bulk_mail": "1",
"bulk_sms": "0",
"bindex": "76",
"f1849": "9898983",
"f1850": "Foundation Course 2: Lecture 1 QUIZ",
"f1851": "0",
"f1853": "John Doe",
"f1854": "TRUE",
"f1855": "93", // <= calculate average for this property
"f1859": "292",
"f1862": "0",
"f1867": "Kajabi",
"f1868": "0",
"unique_id": "7WB77PT"
},
...
]
}
I need to pull out the value for the property named f1855 for each object, then calculate the average and return that value via POST.
I don't think this is hard to do with JavaScript but I am not used to the rules and limits of Zapier.
Any help appreciated!
**Seeing the edited version of your post, I am now not sure if your input data is the data array inside the example or it is an array of those objects. If the second, then I don't know if you want an average for each object's data prop. or something else. But the code below could be part of the solution in either case.
I don't know anything about Zapier, but the JavaScript part could look something like this:
const inputData = //your array of objects
const reducer = (sum, theObject) => sum + parseFloat(theObject.f1855)
const sumF1855 = inputData.reduce(reducer, 0)
const avgF1855 = sumF1855 / inputData.length
This code does not handle error conditions (like if on of the objects were missing the f1855 property or divide by zero if inputData were empty). Hopefully it gives you an idea to get started.

Using nested mapping within Observable subscription - Angular6

I am fetching some data via an API call (within my dataservicse.ts file) - and the response I get is a sort of complex JSON structure. I am able to fetch a specific part of the response as follows in my corresponding Component file as follows -
Here is one of the JSON object response that represents the general structure -
{
"address": {
"building": "1234",
"coord": [0, 0],
"street": "123 Main Street",
"zipcode": "00000"
},
"address2": "Test Suite 000",
"grades": [{
"grade": "A",
"score": 3
}, {
"grade": "B",
"score": 4
}, {
"grade": "A",
"score": 2
}],
"name": "John Doe",
"_id": "1212121"
}
Now - my goal is to acquire the 'name' attribute as well as the first 'grade' value within the grades attribute from each response object - and map them into separate Arrays so that I can display them in table columns using *ngFor.
This is my Component code
export class TestDataComponent implements OnInit {
name$: Object;
grade$: Object;
constructor(private data: DataService, private data2: DataService) { }
ngOnInit() {
//getAPIData is returning the API response from the dataservices.ts file
this.data.getAPIData().subscribe(
data=>console.log(data.response.map(name=>name.name))
); //this works fine - I get the names in an array
this.data2.getAPIData().subscribe(
data2=>console.log((data2.response.map(grade=>grade.grades)).map(grades
=> {grades.map((value, index) => value.grade})) //this returns an undefined value
);
}
Now - if I console.log((data2.response.map(grade=>grade.grades))
I get an Array of Array objects such as -
Array - [Array(3), Array(3), Array(2)]
and each of them consist of the 'grades' attribute Array of objects.
(taking the first array from above) -
Array(3)
0:{"grade": "A","score": 3}
1:{"grade": "B", "score": 4}
2:{"grade": "A", "score": 2}
Thus - I further map my initial response to achieve the 'grade' value. Also - I only want the first grade - thus I have a simple condition added as follows -
console.log((data2.response.map(grade=>grade.grades))
.map(grades
=> {grades.map((value, index) =>{if(index<1) value.grade})}))
As mentioned in the comment - I get an undefined value.
I undestand this issue may be fairly complex but I've tried my best to explain it as clearly as possible. My goal is to get the first 'grade' values from each object and display them in an Array - just as the names, so that I can use it to display it on a table.
I am fairly new to Angular6, just switching from Angular 1.6 - so I am pretty sure I am messing something up.
What would be the best way to get the grade values into an Array by nested mapping within subscribe? Or is there a better approach to the same ?
Also - for the sake of simplicity, ignore the fact that the first subscription is present (for the name attribute) - I showed it here so as to make it clear as to what I want to achieve.
Here is what I think you're asking for since you never gave a concrete example of what you're trying to map/reduce. Also, this is vanilla JavaScript but could easily be translated into RxJS.
// I am assuming that a call to `DataServce.getAPIData()` returns an array
const api_response = [{
"address": {
"building": "1234",
"coord": [0, 0],
"street": "123 Main Street",
"zipcode": "00000"
},
"address2": "Test Suite 000",
"grades": [{
"grade": "A",
"score": 3
}, {
"grade": "B",
"score": 4
}, {
"grade": "A",
"score": 2
}],
"name": "John Doe",
"_id": "1212121"
}];
// Map and pluck the values you need
const result = api_response.map(v => [v.name, v.grades[0].score])
// Or as an array of objects
const obj_result = result.map(([name, score]) => ({ name, score }))
// Is this what you want?
console.log('Array', result);
console.log('Objects', obj_result);
Quick Update
Thanks for accepting the answer. Just wanted to give a quick of what this might look like using RxJS. I say might because the code snippet below is untested.
this.nameAndScores$ = data.getAPIData().pipe(
map(({ response }) => response),
map(({ name, grades }) => ( { name, score: grades[0].score } ))
)
Assuming that nameAndScores$ is a property of your component instance, you can do *ngFor item in nameAndScores$ | async and avoid any explicit subscriptions in your code.
You are using curly braces with a fat arrow in second map function. While using curly braces, you should return a value using the return keyword.

Angular2 filter nested array of objects

I have an array of objects as below. I am trying to achieve search functionality using .filter() method in my angular2 application.
[{
"label": "new",
"result": [{
"label": "new",
"fname": "abc",
"lname": "xyz"
}, {
"label": "new",
"fname": "abc1",
"lname": "xyz1"
}]},
{
"label": "old",
"result": [{
"label": "old",
"fname": "abc2",
"lname": "xyz2"
}]
}]
I am able to achieve parent/one level filtering using below code:
this.data.filter(item => (item.label.toLowerCase().indexOf(inputText) !== -1);
This is returning the object that matches value of label. I want to have filter on 'fname' and 'lname' also.
Your data structure is not well suited for doing an arbitrary string lookup from a 3 level data structure like this.
Your are going to end up having to iterate all three levels - the array of objects, the results array in each object, and all the properties of the objects in the results array.
This is an O(n^3) operation and would likely perform unsatisfactorily with a large data set. You may need to think about how you can better structure this data for this use case.
I would when I get the data from the server create a lookup variable inside every item which contains all text you want to be able to search.
But make sure this is only done once, since it's wouldn't be very performant if you did it for every keystroke.
// Add searchable string to users
users.forEach(u => {
u.filterTerm = `${(u.id || '').toLowerCase()} ${(u.email || '').toLowerCase()} ${(u.firstName || '').toLowerCase()} ${(u.lastName || '').toLowerCase()}`;
});
Then you can use the includes method to filter
let filtered = allUsers.filter(u => {
return u.filterTerm.includes(filterTerm);
});

Backbone: Making Collections out of JSON attributes advice, and making things better

Up until now I've been using .get() and _.clone() on the array attributes then making my object up before setting the model but this feels completely wrong and I'm not sure how to improve upon this. I feel the array attributes should be transformed into collections but 1) I'm not sure how to do this and 2) I'm not sure of the real benefits compared to my current approach. Can anyone help me improve on my approach? Also note, the POST object needs to be created to be sent via AJAX to a non RESTful service
Example model:
{
"id": 1,
"name": "Joe Bloggs",
"teams": [],
"ageGroups": [],
"categories": []
}
Example of how my data should look when posting back to server
{
"id": 1,
"name": "Joe Bloggs",
"teams": [
{
"id": 123,
"name": "Team One",
"location": "UK"
}, {
"id": 321,
"name": "Team Two",
"location": "USA"
}
],
"ageGroups": ["16", "18", "21"],
"categories": [
{
"id": 45,
"name": "Category One"
}, {
"id": 65,
"name": "Category Two"
}
]
}
A very stripped down example:
var myView = new View({
addToCategory: function() {
var categories = _.clone(this.model.get('categories'));
// Grab values I need from user input...
var categoryDetails = {
"id": userId,
"name": userName
};
this.model.set({
categories: categoryDetails
});
},
addToAgeGroups: function() {
var ageGroups = _.clone(this.model.get('ageGroups'));
// Grab my age group via user input ...
ageGroups.push(newAgeGroup);
this.model.set({
ageGroups: ageGroups
});
}
});
Backbone (intentionally?) does not handle nested data particularly well. After some experience I've resisted the urge to make my Model have an attribute on it that is a Collection, etc. It generally makes things more confusing for little benefit.
I suggest making your Models smarter to handle the array attributes, moving some of the logic in your View into the Model itself. If you really think some parts of your application would like to treat that data as a full-blown Collection, have the Model handle that internally too.
For instance:
var MyModel = Backbone.Model.extend({
getCategories: function() {
if (!this._categoriesCollection) {
var categories = this.get('categories');
this._categoriesCollection = new Backbone.Collection(categories);
}
return this._categoriesCollection;
}
addCategory: function(categoryDetails) {
var currentCategories = this.getCategories();
currentCategories.add(categoryDetails);
}
});
Caching the result of getCategories() means you can ensure there is only ever one instance of the collection of categories. You can add more methods to handle the Parent/Child relationship within the Model, making him the sole owner of that data.
Handling how to POST the data to server seems like a separate question, but generally I've overridden the Model.sync() method to do that sort of thing.

Categories