I am an Angular novice and am learning a little by trying to pull the evolution chain for each pokemon using pokeapi but having a difficult time because of deep nesting.
A typical response object is returned like this:
{
"baby_trigger_item": null,
"id": 2,
"chain": {
"evolution_details": [],
"evolves_to": [
{
"evolution_details": [
{
"min_level": 16,
"min_beauty": null,
"time_of_day": "",
"gender": null,
"relative_physical_stats": null,
"needs_overworld_rain": false,
"turn_upside_down": false,
"item": null,
"trigger": {
"url": "http://pokeapi.co/api/v2/evolution-trigger/1/",
"name": "level-up"
},
"known_move_type": null,
"min_affection": null,
"party_type": null,
"trade_species": null,
"party_species": null,
"min_happiness": null,
"held_item": null,
"known_move": null,
"location": null
}
],
"evolves_to": [
{
"evolution_details": [
{
"min_level": 36,
"min_beauty": null,
"time_of_day": "",
"gender": null,
"relative_physical_stats": null,
"needs_overworld_rain": false,
"turn_upside_down": false,
"item": null,
"trigger": {
"url": "http://pokeapi.co/api/v2/evolution-trigger/1/",
"name": "level-up"
},
"known_move_type": null,
"min_affection": null,
"party_type": null,
"trade_species": null,
"party_species": null,
"min_happiness": null,
"held_item": null,
"known_move": null,
"location": null
}
],
"evolves_to": [],
"is_baby": false,
"species": {
"url": "http://pokeapi.co/api/v2/pokemon-species/6/",
"name": "charizard"
}
}
],
"is_baby": false,
"species": {
"url": "http://pokeapi.co/api/v2/pokemon-species/5/",
"name": "charmeleon"
}
}
],
"is_baby": false,
"species": {
"url": "http://pokeapi.co/api/v2/pokemon-species/4/",
"name": "charmander"
}
}
}
I have to get to evolves_to property, and grab species.name as well as evolution_details.min_level and evolution_details.trigger.name, and evolution_details.item if not null
But as you can see, the evolves_to property, itself contains another evolves_to nested inside, which has another nested inside
This is my sad attempt (after http.get) and I'm just stuck now.
var evoObject = response.data;
function loopEvo(obj){
angular.forEach(obj, function(value, key, object){
if (key == 'evolves_to' && value != []) {
//from here I can get top level data, but...
}
});
}
loopEvo(evoObject.chain);
I don't know how to recursively dive into objects and continually grab data, can anyone provide any help? I would love to use this as a great learning opportunity in traversing complex json objects.
You could always just avoid using Angular and stick with plain JS to build out your evolution chain... try giving this a go, it was based on your angular for loop. This should leave you with an array (evoChain) of the objects containing the data you are looking for ordered from first evolution at 0 index to last evolution at the last index.
var evoChain = [];
var evoData = response.data.chain;
do {
var evoDetails = evoData['evolution_details'][0];
evoChain.push({
"species_name": evoData.species.name,
"min_level": !evoDetails ? 1 : evoDetails.min_level,
"trigger_name": !evoDetails ? null : evoDetails.trigger.name,
"item": !evoDetails ? null : evoDetails.item
});
evoData = evoData['evolves_to'][0];
} while (!!evoData && evoData.hasOwnProperty('evolves_to'));
In your sample case above the resulting array should appear as follows:
[{
"species_name": "charmander",
"min_level": 1,
"trigger_name": null,
"item": null
}, {
"species_name": "charmeleon",
"min_level": 16,
"trigger_name": "level-up",
"item": null
}, {
"species_name": "charizard",
"min_level": 36,
"trigger_name": "level-up",
"item": null
}]
The approved answer above does not work if there are multiple evolutions such as Eevee or Snorunt. That will only return the first evolution e.g. Vaporeon
The following checks number of evolutions and runs through them all.
let evoChain = [];
let evoData = chain.chain;
do {
let numberOfEvolutions = evoData['evolves_to'].length;
evoChain.push({
"species_name": evoData .species.name,
"min_level": !evoData ? 1 : evoData .min_level,
"trigger_name": !evoData ? null : evoData .trigger.name,
"item": !evoData ? null : evoData .item
});
if(numberOfEvolutions > 1) {
for (let i = 1;i < numberOfEvolutions; i++) {
evoChain.push({
"species_name": evoData.evolves_to[i].species.name,
"min_level": !evoData.evolves_to[i]? 1 : evoData.evolves_to[i].min_level,
"trigger_name": !evoData.evolves_to[i]? null : evoData.evolves_to[i].trigger.name,
"item": !evoData.evolves_to[i]? null : evoData.evolves_to[i].item
});
}
}
evoData = evoData['evolves_to'][0];
} while (!!evoData && evoData.hasOwnProperty('evolves_to'));
return evoChain;
brandudno is correct: the extra if(numberOfEvolutions) is the more complete approach (THANKS #brandudno! This really helped me solve for ALL the use cases - including eevee!)
I like the use of !!evoData in the while statement now that I took the time to understand it, but it was confusing for me at 1st, so I made a minor modification that still works and may be easier for other new developers (continues until evoData becomes undefined).
Lastly, I made a minor change in case others also prefer to use the . annotation (evoData.evolves_tovs.evoData['evolves_to']`) to take advantage of autocomplete, etc.
let evoChain = [];
let evoData = chain.chain;
do {
let numberOfEvolutions = evoData.evolves_to.length;
evoChain.push({
"species_name": evoData .species.name,
"min_level": !evoData ? 1 : evoData .min_level,
"trigger_name": !evoData ? null : evoData .trigger.name,
"item": !evoData ? null : evoData .item
});
if(numberOfEvolutions > 1) {
for (let i = 1;i < numberOfEvolutions; i++) {
evoChain.push({
"species_name": evoData.evolves_to[i].species.name,
"min_level": !evoData.evolves_to[i]? 1 : evoData.evolves_to[i].min_level,
"trigger_name": !evoData.evolves_to[i]? null : evoData.evolves_to[i].trigger.name,
"item": !evoData.evolves_to[i]? null : evoData.evolves_to[i].item
});
}
}
evoData = evoData.evolves_to[0];
} while (evoData != undefined && evoData.hasOwnProperty('evolves_to'));
return evoChain;
I am using a recursive function to solve this.
Here's how it goes with plain JavaScript.
let evoChain = [];
function getEvo(arr) {
if (arr[0].evolves_to.length > 0) {
evoChain.push(arr[0].species.name);
getEvo(arr[0].evolves_to);
} else {
evoChain.push(arr[0].species.name);
return 0;
}
}
getEvo([data.chain]);```
Related
I am new to sequelize i am trying to get comments and the replies to those comments by hasMany association and have to count the parent comments and the replies to each comment that i am getting
I am using sequelize findAndCountAll to count the parent comment rows which works fine now counting replies of each comment i am using
attributes:{include: [[mysql.sequelize.fn("COUNT", mysql.sequelize.col("comment.comment_id")), "Count"]]}
Which count the replies of the first comment but does get gives the rest of the parent comments
model
const comment = model.comment;
comment.hasMany(comment,
{
foreignKey: 'parentCommentId',
as: "replies"
});
query
function findWhere(where, pagination){
return mysql.comment.findAndCountAll({
where,
offset: pagination.offset,
limit: pagination.count,
// attributes:{include: [[mysql.sequelize.fn("COUNT", mysql.sequelize.col("comment.comment_id")), "Count"]]},
include: [{
model: mysql.comment,
as: "replies",
offset: 0,
limit: 1
}]
});
}
This is the reply that it gives
{
"count": 10,
"rows": [
{
"commentId": 2,
"userId": 7,
"postId": 1,
"mediaId": null,
"commentText": "2 Updated Wao amazing post 13",
"parentCommentId": null,
"createdAt": "2019-07-01T05:20:00.000Z",
"updatedAt": "2019-07-04T02:35:49.000Z",
"Count": 10,
"replies": [
{
"commentId": 4,
"userId": 7,
"postId": null,
"mediaId": null,
"commentText": "Wao amazing post",
"parentCommentId": 2,
"createdAt": "2019-07-02T04:08:29.000Z",
"updatedAt": "2019-07-02T04:08:29.000Z"
}
]
}
]
}
Which is wrong as it does not shows the rest of the parent comments
The issue is in this
attributes:{include: [[mysql.sequelize.fn("COUNT", mysql.sequelize.col("comment.comment_id")), "Count"]]}
added beforeCount hook inside the DB connection file this snippet will fix the wrong count issue in findAndCountAll()
sequelize.addHook("beforeCount", function (options) {
const includeAvailable = options.include && options.find((item) => item["where"]);
if (this._scope.include && this._scope.include.length > 0) {
options.distinct = true;
options.col =
this._scope.col || options.col || `"${this.options.name.singular}".id`;
}
const whereSymbols = includeAvailable
? Object.getOwnPropertySymbols(includeAvailable["where"]).length
: 0;
const whereString = includeAvailable
? JSON.stringify(includeAvailable["where"]).length
: 0;
const whereObject = whereSymbols > 0 ? whereSymbols : whereString > 2 ? 1 : 0;
if (whereObject === 0 && options.include && options.include.length > 0) {
options.include = null;
}
});
I am using the following code to call an API and return results:
api.jobs.all(function(response) {
const obj = response.data.map(function(item) {
return [item.id, item.billed.amountString];
});
});
With the following JSON:
{
"data": [
{
"id": 2090170,
"deadline": null,
"jobId": {
"id": 1644
},
"billed": {
"amountString": 200,
"currencyType": "CAD"
}
},
{
"id": 2090171,
"deadline": null,
"jobId": {
"id": 1645
},
"billed": {
"amountString": 400,
"currencyType": "USD"
}
}]}
The code is working fine, for the most part I am getting back good results, with the exception of: billed.amountString
I keep getting the following error:
TypeError: Cannot read property 'amountString' of null
Can anyone see why this would be returning null?
Also, is there a way in which I could loop through the API call and force it to do the following:
If .amountString === null, .amountString = "";
var response = {
"data": [
{
"id": 2090170,
"deadline": null,
"jobId": {
"id": 1644
},
"billed": {
"amountString": 200,
"currencyType": "CAD"
}
},
{
"id": 2090171,
"deadline": null,
"jobId": {
"id": 1645
},
"billed": {
"amountString": 400,
"currencyType": "USD"
}
}]};
const obj = (response.data).map(function(item) {
return [item.id, item.billed.amountString];
});
console.log(obj);
You could use the library lodash. The lodash method get can be used to try and access an object field. If it does not exist you can specify a default return value. See https://lodash.com/ .
// This will try to access item.billed.amountString
// If an item does not exist anywhere along the way
// it will return the default.
// _.get( OBJECT, PATH, DEFAULT )
_.get(item, ['billed', 'amountString'], '')
I use strongloop to build my api.
On a particular route the query includes model's relations. I get an array of objects that I would like to arrange.
In this particular arranging function I face the following problem.
The function receive an object named "item" containing a "trans" field (this field is an array of another object).
this piece of code :
console.log(JSON.stringify(item, null, 2));
produces this result :
{
"id": 1,
"created": "2015-08-19T21:04:16.000Z",
"updated": null,
"authorid": 0,
"likes": 0,
"shares": 0,
"fav": 0,
"validated": 0,
"comments": 0,
"trans": [
{
"text": "Première question en français",
"questionId": 1
}
],
"answers": [
{
"id": 1,
"questionid": 1,
"questionId": 1,
"trans": [
{
"text": "q1 : reponse 1 en francais",
"answerId": 1
}
]
},
{
"id": 2,
"questionid": 1,
"questionId": 1,
"trans": [
{
"text": "q1 : reponse 2 en francais",
"answerId": 2
}
]
}
]
}
This problem is when I try to reach this part :
item.trans[0].text
console says "item.trans is undifined" and when I try this piece of code :
console.log(item.trans);
I have this result :
function (condOrRefresh, options, cb) {
if (arguments.length === 0) {
if (typeof f.value === 'function') {
return f.value(self);
} else if (self.__cachedRelations) {
return self.__cachedRelations[name];
}
} else {
if (typeof condOrRefresh === 'function'
&& options === undefined && cb === undefined) {
// customer.orders(cb)
cb = condOrRefresh;
options = {};
condOrRefresh = undefined;
} else if (typeof options === 'function' && cb === undefined) {
// customer.orders(condOrRefresh, cb);
cb = options;
options = {};
}
options = options || {}
// Check if there is a through model
// see https://github.com/strongloop/loopback/issues/1076
if (f._scope.collect &&
condOrRefresh !== null && typeof condOrRefresh === 'object') {
//extract the paging filters to the through model
['limit','offset','skip','order'].forEach(function(pagerFilter){
if(typeof(condOrRefresh[pagerFilter]) !== 'undefined'){
f._scope[pagerFilter] = condOrRefresh[pagerFilter];
delete condOrRefresh[pagerFilter];
}
});
// Adjust the include so that the condition will be applied to
// the target model
f._scope.include = {
relation: f._scope.collect,
scope: condOrRefresh
};
condOrRefresh = {};
}
return definition.related(self, f._scope, condOrRefresh, options, cb);
}
}
How can I simply access the "trans" property in this case to get the text inside ?
(Not really at easy in js)
Thanks in advance.
It's possible that your item object has implemented the toJSON function.
Pop open your browser's console and run this snippet to see an example of how this can make a difference between the stringified JSON and the actual object:
var x = {
name: "foo",
children : function() {
return [ { name: 'child 1' }, { name: 'child 2' } ];
},
toJSON: function() {
var simplified = { name: this.name, children: this.children() };
return simplified
}
};
// shows children as a simple array
console.log( JSON.stringify( x, null, 2 ) );
// {
// "name": "foo",
// "children": [
// {
// "name": "child 1"
// },
// {
// "name": "child 2"
// }
// ]
// }
// oops... not what you expected
console.log( x.children[0].name );
// Uncaught TypeError: Cannot read property 'name' of undefined
Of course, the easiest fix would be to parse the stringify result:
var y = JSON.parse( JSON.stringify( x ) );
console.log( y.children[0].name );
It's a last-case-scenario-type solution, though, since JSON.stringify is a very expensive function.
I am getting the following error:
2015-05-06T15:51:43.332-0700 map reduce failed:{
"errmsg" : "exception: TypeError: Cannot read property 'Dockey' of undefined near 'essaged[0].Dockey) { return; ' ",
"code" : 16722,
"ok" : 0
} at src/mongo/shell/collection.js:1224
Here is the script I am running it is a Map Reduce script run against mongo db.
function mapFunction() {
if (!this.eventdata || !this.eventdata.Messaged || this.eventdata.Messaged.length === 0 || !this.eventdata.Messaged[0].Dockey) {
return;
}
emit({
entityid: this.entityid,
eventType: this.eventType,
profileid: (this.eventdata.Messaged[0].Dockey || this.eventdata.Messaged[0].PID)
}, {
datecreated: this.datecreated
});
}
function reduceFunction(key, values) {
var reducedValue = values[0],
i;
for (i = 1; i < values.length; i++) {
if (values[i].datecreated > reducedValue.datecreated) {
reducedValue = values[i];
}
}
return reducedValue;
}
var res = db.TRACKING_DATA.mapReduce(mapFunction,
reduceFunction, {
out: {
reduce: "LASTCONNECTED_ALLTYPES"
},
query: {
datecreated: {
$gt: ISODate("2015-03-11T18:00:00.000Z")
}
},
}
)
Here is a doc entry from the collection:
"_id": "0003e88f-37ed-493c-930e-f401821faca1",
"_class": "com.daoservice.model.TrackingData",
"modified": ISODate("2015-03-03T22:00:04.679Z"),
"eventtype": "#Connect button clicked and default email tab opened",
"eventdata": {
"Messaged": [{
"ViewedID": "4918faab-2eca-4b36-8ca7-342f064a4699",
"Dockey": "ad30d3c827e2f1f44d4bae598ac14093",
"PID": "",
"UID": "",
"connectDate": ISODate("2015-03-03T22:00:04.676Z"),
"connectID": "fcd6d2cb-1f2d-43ac-b42d-deec7f5d7931",
"urlClicked": "",
"email": ""
}]
},
"eventsource": "Customer App",
"sourceip": "210.7.77.202",
"entityid": "1652430",
"groupid": "15013",
"datecreated": ISODate("2015-03-03T22:00:04.674Z")
The Messaged array has no members, but you're trying to directly access one in your last condition check !this.eventdata.Messaged[0].Dockey. Validate that the Messaged array has the member you're trying to access before doing so.
!this.eventdata.Messaged[0]
And your final condition should look like
if (!this.eventdata || !this.eventdata.Messaged || this.eventdata.Messaged.length === 0 || !this.eventdata.Messaged[0] || !this.eventdata.Messaged[0].Dockey) {
The essence is that I have json file:
[
{
"id": 0,
"username": "Antony",
"users": [
{
"id": 1,
"like": 0
},
{
"id": 2,
"like": 1
},
{
"id": 3,
"like": 0
},
{
"id": 4,
"like": 1
}
]
},
{
"id": 1,
"username": "Janet",
"users": [
{
"id": 0,
"like": 0
},
{
"id": 2,
"like": 1
},
{
"id": 3,
"like": 1
},
{
"id": 4,
"like": 1
}
]
},.......
I need to count how many "likes", have each user.
ie:
For example, take the first id == 0.
We pass on the object, which can be very much and look:
If id == 0 and like == 1, add 1 to the array.
In the end, I must have:
usersWithLikes [id User] = number of likes for all objects
For example:
usersWithLikes [0] = 3
usersWithLikes [1] = 1
usersWithLikes [2] = 4
usersWithLikes [3] = 0
At the moment, I think so:
thumbsUp_data - json data
var usersWithLikes = thumbsUp_data.map(function(user_data){
return user_data.users.filter(function(value){
return value.like == 1;
}).length;
});
But this is not correct, because it considers how many likes the object.
Help with the decision ...
Filter out the user object, grab the first element of the returned array and then filter on that object's user array for like === 1 returning it's length;
function howManyLikes(id) {
return arr.filter(function (user) {
return user.id === id;
})[0].users.filter(function (el) {
return el.like === 1;
}).length;
}
howManyLikes(1); // 3
DEMO
thumbsUp_data.forEach(function(data) {
data.users.forEach(function(value) {
usersWithLikes[value.id] = usersWithLikes[value.id] || 0;
usersWithLikes[value.id] += value.like;
});
});
Thats all, it's a solution!