JSON/jQuery - moving between child and parent objects - javascript

I'm currently building a tool for the card game Hearthstone. If you're familiar with the game it's basically a tool that allows you to add your in game deck to a list so that you can monitor what cards you have left at all times along with the chance of drawing X card etc. Nothing too fancy but since I am a huge novice to the world of web development I'm using it as an exercise to help me learn more.
Anyway on to my problem!
At the moment I have a JSON database that has every card in hearthstone along with all of the different parameters associated with each card such as name, cost, playerClass etc.
I have figured out how to retrieve objects from the database but only via the name, since that's what players will use to search for the card they want to add to their deck.The problem I have at the moment is that the name of the card is a child of the card object which is itself a child of the card set object (basic, classic, naxx, gvg etc)
I would like to get ALL of the card data back when I search for it by name but try as I might, I can't figure out how to talk to a parent object via it's child.
to start with here is the search function from the users input:
$.getJSON("json/AllSets.json",function(hearthStoneData){
$('.submit').click(function(){
var searchValue = $('#name').val();
var returnValue = getObjects(hearthStoneData, "name", searchValue);
console.log(hearthStoneData);
console.log(returnValue);
});
});
and here is the request from the database:
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key].toLowerCase() == val.toLowerCase()) {
objects.push(obj[i]);
}
}
return objects;
}
And finally here is an example of one of the JSON cards I am trying to talk to.
{
"id":"EX1_066","name":"Acidic Swamp Ooze",
"type":"Minion",
"faction":"Alliance",
"rarity":"Common",
"cost":2,
"attack":3,
"health":2,
"text":"<b>Battlecry:</b> Destroy your opponent's weapon.",
"flavor":"Oozes love Flamenco. Don't ask.",
"artist":"Chris Rahn",
"collectible":true,
"howToGetGold":"Unlocked at Rogue Level 57.",
"mechanics":["Battlecry"]}
So the output im getting when I console log is something like this:
Object {Basic: Array[210], Classic: Array[387], Credits: Array[17], Curse of Naxxramas: Array[160], Debug: Array[58]…}
Basic: Array[210]
[0 … 99]
6: Object
artist: "Howard Lyon"
collectible: true
cost: 2
faction: "Neutral"
flavor: "This spell is much better than Arcane Implosion."
howToGet: "Unlocked at Level 1."
howToGetGold: "Unlocked at Level 28."
id: "CS2_025"
name: "Arcane Explosion"
playerClass: "Mage"
rarity: "Free"
text: "Deal $1 damage to all enemy minions."
type: "Spell"
As you can see, there are several nested arrays before you actually get to the card. I can sort of visualise in my head what I think needs to be done but I definitely dont feel certain. Also alot of the syntax has been copy/pasted and modified to suit my needs, I'm still a total beginner with this stuff and really have no idea how I would write a fix to this problem myself.
Any help is hugely appreciated.
Thanks

I think there's a problem with how the data is stored:
Every object needs to have a unique id
Every Child object needs to return a reference to the parentId. This needs to be stored on insert or creation of the child object.
There needs to be a way to look up any object by id

Related

Use multiple key-value filters on an object of objects?

Bit of a lengthy one so those of you who like a challenge (or I'm simply not knowledgeable enough - hopefully it's an easy solution!) read on!
(skip to the actual question part to skip the explanation and what I've tried)
Problem
I have a site that has a dataset that contains an object with multiple objects inside. Each of those objects contains an array, and within that array there are multiple objects. (yes this is painful but its from an API and I need to use this dataset without changing or modifying it.) I am trying to filter the dataset based of the key-value pairs in the final object. However, I have multiple filters being executed at once.
Example of Path before looping which retrieves the key-value pair needed for one hall.
["Hamilton Hall"]["Hire Options"][2].Commercial
After Looping Path of required key-value pair for all halls, not just one (the hall identifier is stored):
[0]["Hire Options"][2].Commercial
Looping allows me to check each hall for a specific key-value pair (kind of like map or forEach, but for an object).
After getting that out of the way back to the question.
How would I go about filtering which of the looped objects are displayed?
What I have Tried
(userInput is defined elsewhere - this happens on a btn click btw)
let results = Object.keys(halls);
for (key of results) {
let weekend = [halls[ `${key}` ][ 'Hire Options' ][4][ 'Weekend function' ]];
if(userInput == weekend) {
outputAll([halls[ `${key}` ]]);
}
}
That filters it fine. However, I run into an issue here. I want to filter by multiple queries, and naturally adding an AND into the if statement doesn't work. I also dont want to have 10 if statements (I have 10+ filters of various data types I need to sort by).
I have recently heard of ternary operators, but do not know enough about them to know if that is the correct thing to do? If so, how? Also had a brief loook at switches, but doesnt seem to look like what I want (correct me if I am wrong.)
Actual Question minus the babble/explanation
Is there a way for me to dynamically modify an if statements conditions? Such as adding or removing conditions of an if statement? Such as if the filter for 'a' is set to off, remove the AND condition for 'a' in the if statement? This would mean that the results would only filter with the active filters.
Any help, comments or 'why haven't you tried this' remark are greatly appreciated!
Thanks!
Just for extra reference, here is the code for retrieving each of the objects from the first object as it loops through them:
(Looping Code)
halls = data[ 'Halls' ];
let results = Object.keys(halls);
for (key of results) {
let arr = [halls[ `${key}` ]];
outputAll(arr);
}
You can use Array.filter on the keys array - you can structure the logic for a match how you like - just make it return true if a match is found and the element needs to be displayed.
let results = Object.keys(halls);
results.filter(key => {
if (userInput == halls[key]['Hire Options'][4]['Weekend function']) {
return true;
}
if (some other condition you want to match) {
return true;
}
return false;
}).forEach(key => outputAll([halls[key]]));

Aggregate values in a javascript grouped object based on conditions

I am using IE 11.
I have an object array that is grouped using the lodash library. I want to be able to query the object and based on certain conditions come up with sums/counts. So for example, I have this object array.
I would like to have the result seen below but in an object like the image above
As you can see, each company in the group should have certain values based on the following criteria
How many times does 'company x' have a Total Count >3?
How many times does 'company x' have expectingFunding eq ‘Yes’>
How many times does 'company x' have fundedOnIKNS eq ‘No’?
I've tried quite a bit in the last couple of days but not success. I first declared 2 arrays so I can capture the unique values of company name and program. I also created an object to update when conditions were met. The only successful thing I was able to get was to keep it in an grouped object. All the values in the new object were wrong.
Here's an excerpt of the code:
const companiesSummary = {};
for (const company of Object.keys(myData)) {
companiesSummary[company] = {
totalCount: 0,
expectedFunding: 0,
IKNSFunding: 0,
};
for (const { TotalCount, expectedFunding, fundedOnIKNS } of myData[company]) {
companiesSummary[company].totalCount += TotalCount;
companiesSummary[company].expectedFunding += expectedFunding === "Yes";
companiesSummary[company].fundedOnIKNS += fundedOnIKNS === "Yes";
}
}
I get the error,
TypeError: myData[company] is not iterable
Here's a link to the pen
I would still like the result to be in an object array, so I can create an html table later. Any help would be much appreciated. Thank you!
Your code isn't working because you're taking myData, an array, accessing myData[company], an object (company is 0, 1, ...), and you can't iterate through an object with for...of. myData is definitely not the same object in your screenshot.
Your code excerpt might work if your myData object were the object in your screenshot.

Efficient Sorted Data Structure in JavaScript

I'm looking for a way to take a bunch of JSON objects and store them in a data structure that allows both fast lookup and also fast manipulation which might change the position in the structure for a particular object.
An example object:
{
name: 'Bill',
dob: '2014-05-17T15:31:00Z'
}
Given a sort by name ascending and dob descending, how would you go about storing the objects so that if I have a new object to insert, I know very quickly where in the data structure to place it so that the object's position is sorted against the other objects?
In terms of lookup, I need to be able to say, "Give me the object at index 12" and it pulls it quickly.
I can modify the objects to include data that would be helpful such as storing current index position etc in a property e.g. {_indexData: {someNumber: 23, someNeighbour: Object}} although I would prefer not to.
I have looked at b-trees and think this is likely to be the answer but was unsure how to implement using multiple sort arguments (name: ascending, dob: descending) unless I implemented two trees?
Does anyone have a good way to solve this?
First thing you need to do is store all the objects in an array. That'll be your best bet in terms of lookup considering you want "Give me the object at index 12", you can easily access that object like data[11]
Now coming towards storing and sorting them, consider you have the following array of those objects:
var data = [{
name: 'Bill',
dob: '2014-05-17T15:31:00Z'
},
{
name: 'John',
dob: '2013-06-17T15:31:00Z'
},
{
name: 'Alex',
dob: '2010-06-17T15:31:00Z'
}];
The following simple function (taken from here) will help you in sorting them based on their properties:
function sortResults(prop, asc) {
data = data.sort(function(a, b) {
if (asc) return (a[prop] > b[prop]);
else return (b[prop] > a[prop]);
});
}
First parameter is the property name on which you want to sort e.g. 'name' and second one is a boolean of ascending sort, if false, it will sort descendingly.
Next step, you need to call this function and give the desired values:
sortResults('name', true);
and Wola! Your array is now sorted ascendingly w.r.t names. Now you can access the objects like data[11], just like you wished to access them and they are sorted as well.
You can play around with the example HERE. If i missed anything or couldn't understand your problem properly, feel free to explain and i'll tweak my solution.
EDIT: Going through your question again, i think i missed that dynamically adding objects bit. With my solution, you'll have to call the sortResults function everytime you add an object which might get expensive.

Dojo: Dealing with a priority list (ordered list) when using a Store

Ice cream! I want to make a list of all ice creams I have tried and keep the list ordered so my favourite ice creams are at the top and my least favourite are at the bottom. (The list should be ordered by how delicious the ice cream is.
If I model it in an array I can do something like this:
var iceCreamOrdredList = [
{name: "Coconut"},
{name: "Mint Chocolate Chip"},
...
{name: "Banana"} /* Obviously noone really likes banana ice cream*/
}
The order of the array indicates that Coconut ice cream is preferred to Banana.
If I want to display my ice cream preferences in a dijit or dgrid, then I need to wrap the array in a store. A memory store is probably the most obvious choice. That means doing something like this:
var iceCreamStore = new Memory([data: iceCreamOrdredList]);
Whenever I try a new ice cream I want to add it to this data structure and allow all of my dijits/dgrids to update. This means that I should probably declare my store like this instead:
var iceCreamStore = new Observable(new Memory({data: iceCreamOrdredList}));
This for the most part works well.
One issue I haven't been able to resolve is if someone asks me something like this how to answer it:
'Root Beer' ice cream is my favourite. What is the position of 'Root Beer' in your priority list?
I can find 'Root Beer' in the store by doing this:
var someIceCream = iceCreamStore.query({name: "Root Beer"});
But this does not allow me to access the position of 'Root Beer' in the store.
I could also do something like this:
for(var i=0; i<iceCreamStore.data.length; i++){
if(iceCreamStore.data[i].name == "Root Beer"){
return i;
}
}
But this seems 1) Heavy 2) Out of the spirit of the store API.
Doing the opposite is also tricky. Answering the question:
What is your nth favourite ice cream
Requires something along the lines of:
return iceCreamStore.data[n]
This seems very unstore like.
Reordering elements in the store is also challenging. Are there any good patterns for dealing with reordering? I could attach a priority field onto ice cream. Ie:
{name: "Cotton candy", order: 33}
But this means that when I insert or delete an ice cream I will need to update the order property on all ice creams. Yuck.
If the ice cream names are unique, then I would set the Memory Stores idProperty field to "name". You can then do stuff like iceCreamStore.index["Root Beer"] because dojo stores keep an internal index on what position a certain id is at. Here is an example based on your code:
var iceCreamStore = new Observable(new Memory({data: iceCreamOrdredList, idProperty: "name"}));
And to get a particular index (which should be the same as your order), would be:
var priority = iceCreamStore.index["Root Beer"];
The best solution I found to this is to use an "OrderedStore". One implementation of this can be found in the DGrid test code. See here (see line 292):
https://github.com/SitePen/dgrid/blob/c8f08142b9ece66401dc3eef35371f4e98fa996e/test/data/base.js
To briefly summarize, an ordered store is a store which adds behaviour to the add, put and query methods of a store. On add and put the store must update the order property on it's children.
This an extract of the code:
return Observable(new Memory(lang.mixin({data: data,
put: function(object, options){
object.order = calculateOrder(this, object, options && options.before);
return Memory.prototype.put.call(this, object, options);
},
// Memory's add does not need to be augmented since it calls put
copy: function(object, options){
/* Do this if you want copy functionality. */
},
query: function(query, options){
options = options || {};
options.sort = [{attribute:"order"}];
return Memory.prototype.query.call(this, query, options);
}
}, options)));

Dealing with a JSON object too big to fit into memory

I have a dump of a Firebase database representing our Users table stored in JSON. I want to run some data analysis on it but the issue is that it's too big to load into memory completely and manipulate with pure JavaScript (or _ and similar libraries).
Up until now I've been using the JSONStream package to deal with my data in bite-sized chunks (it calls a callback once for each user in the JSON dump).
I've now hit a roadblock though because I want to filter my user ids based on their value. The "questions" I'm trying to answer are of the form "Which users x" whereas previously I was just asking "How many users x" and didn't need to know who they were.
The data format is like this:
{
users: {
123: {
foo: 4
},
567: {
foo: 8
}
}
}
What I want to do is essentially get the user ID (123 or 567 in the above) based on the value of foo. Now, if this were a small list it would be trivial to use something like _.each to iterate over the keys and values and extract the keys I want.
Unfortunately, since it doesn't fit into memory that doesn't work. With JSONStream I can iterate over it by using var parser = JSONStream.parse('users.*'); and piping it into a function that deals with it like this:
var stream = fs.createReadStream('my.json');
stream.pipe(parser);
parser.on('data', function(user) {
// user is equal to { foo: bar } here
// so it is trivial to do my filter
// but I don't know which user ID owns the data
});
But the problem is that I don't have access to the key representing the star wildcard that I passed into JSONStream.parse. In other words, I don't know if { foo: bar} represents user 123 or user 567.
The question is twofold:
How can I get the current path from within my callback?
Is there a better way to be dealing with this JSON data that is too big to fit into memory?
I went ahead and edited JSONStream to add this functionality.
If anyone runs across this and wants to patch it similarly, you can replace line 83 which was previously
stream.queue(this.value[this.key])
with this:
var ret = {};
ret[this.key] = this.value[this.key];
stream.queue(ret);
In the code sample from the original question, rather than user being equal to { foo: bar } in the callback it will now be { uid: { foo: bar } }
Since this is a breaking change I didn't submit a pull request back to the original project but I did leave it in the issues in case they want to add a flag or option for this in the future.

Categories