I am almost there with this but cannot seem to get this functionality going as planned.
I have json 'jsonData' it contains formula of different terms
"jsonData":{
"a" : "b + c",
"b" : "d + e",
"d" : "h + i",
"c" : "f + g"
}
What I am trying to do is to have a function pass one arguments 'mainItem'(ie. one of the key in 'jsonData' for example a in 'jsonData'). Within this function it will get the formula from the json data(for example a is the 'mainitem' and b + c is the formula) and check the dependency of child component of the formula i.e it will check whether b and c have any dependency down the line in json data. If it has any dependency it will be added as a child component to the parent for example if b have a formula in json data. b will be added as the 'mainitem' in the child component of the parent 'mainitem' a. At the end of the code, this is what I would like to get.
{
mainitem : "a",
formula : "b+c",
childComponent: {
mainitem: "b",
formula : "d+e",
childcomponent: {
mainitem: "d",
formula : "h+i"
}
},
{
mainitem: "c",
formula : "f+g"
},
}
The issue is that I am able to create the parent object. But I have no idea how to create the child component for the parent and If the child component have sub child it will also embedded as the child of the child component and so on. It is like a parent child hierarchy series
function getJson(mainItem) {
var json = {};
json['mainitem'] = mainItem;
$.each(jsonData, function(key, value){
if(mainitem == key){
json['formula'] = value;
}
})
}
Any insight into this would highly be appreciated. Thank you.
You need/could to write a recursive function that splits up the "formula" into each composing component/item and then check each component/item for their dependencies.
Here is a solution for you: http://jsfiddle.net/mqchen/4x7cD/
function getJson(item, data) {
if(!data["jsonData"].hasOwnProperty(item)) return null;
var out = {
mainItem: item,
formula: data["jsonData"][item]
};
// Break up formula
var components = out.formula.split(" ");
for(var i = 0; i < components.length; i++) {
var child = getJson(components[i], data); // Recursive call to get childComponents
if(child !== null) {
out["childComponent"] = out["childComponent"] == undefined ? [] : out["childComponent"];
out["childComponent"].push(child);
}
}
return out;
}
// Call it
getJson("a", data)
Note: It does not consider circular dependencies, i.e. if you have a: "b + c", b: "d + a".
This is a dependency problem. I can already tell you ahead of time that you will need to figure out a way to handle circular dependencies (you don't want to get thrown into an infinite loop/recursion when trying to generate the output). Let's go through the basic algorithm. What are some things we need?
We need a way to parse the formulas so that they're meaningful. For this example, I'm gonna assume that we'll get an input that's of the form a + b + c, where I only expect the item and + to delimit each item.
We need a way to recurse down an item's dependencies to create nested childcomponents.
// assuming jsonData is available in this scope
function getStructure (elem) {
elem = $.trim(elem);
// handling an element that doesn't exist in jsonData
if (!jsonData[elem]) {
return {
'mainitem': elem,
'formula': 'none' // or whatever you want to put
};
}
var result = {},
formula = jsonData[elem],
children = formula.split('+'),
i;
result['mainitem'] = elem;
result['formula'] = formula;
// or however you want to store the child components
result['childComponent'] = [];
for (i = 0; i < children.length; i += 1) {
result['childComponent'].push(getStructure(children[i]));
}
return result;
}
Yeah this is some sort of building Syntax Tree as in classic parser/compiler problem.
Any ways I have written this simple recursive function that does what you want. Though if your goal is to build some sort of parser then you must think of following parser / compiler building principles since that will keep things manageable and graspable once functions start growing.
function getJson(mainitem,outjson)
{
formula = jsonData[mainitem];
outjson.mainitem = mainitem;
if (formula != null)
{
outjson.formula = formula;
var firstItem = formula.toString().charAt(0);
var secondItem = formula.charAt(formula.length - 1);
outjson.firstchild = {};
outjson.secondchild = {};
getJson(firstItem, outjson.firstchild);
getJson(secondItem, outjson.secondchild);
}
}
What all you have to do is to create an empty object and pass it to getJson() along with the operand in problem i.e. mainitem:
var outjson = {};
getJson("a", outjson);
I have used JSON library to convert outjson object to JSON text.
Also I have logged this outjson so that you can examine it in the embedded firebug lites' Console window.
Find it at JSFiddle.
There is approved answer already but here are my 5 cents.
as #Mahesha999 said you need to build "Syntax Tree as in classic parser/compiler".
For some theory + examples look at those videos
They are focused on antlr but also contains a lot theory about parsers. Also antlr have javascript plugin that can be used.
I think it's better than any expression evaluation.
Related
I'm trying to create a simple display of NBA west leaders in order by seed using the following json file:
http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json
Right now I have the following:
$(document).ready(function() {
$.getJSON('http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json',function(info){
var eastHead = info.sta.co[0].val;
var divi = info.sta.co[0].di[0].val;
/*evaluate East*/
for(i=0;i < divi.length;i++){
var visTeam ='<li>' + divi + '</li>';
document.getElementById("eastHead").innerHTML=eastHead;
}
var seed = info.sta.co[0].di[0].t[0].see;
$.each(menuItems.data, function (i) {
var eastSeed ='<li>' + seed + '</li>';
console.log(eastSeed)
document.getElementById("eastSeed").innerHTML=eastSeed;
});//$.each(menuItems.data, function (i) {
});//getJSON
});//ready
I'm looking just to list out the leaders in order. So right now we have
Golden State 2. Memphis 3. Houston 4. Portland 5. L.A. Clippers 6. Dallas .... and so
forth.
This is based off of the "see" value which means seed in the west.
This issue is I'm getting a single value rather than an iteration.
Updated:
$(document).ready(function() {
$.getJSON('http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json',function(info){
/**************************************************/
//Get info above here
var westDivision = info.sta.co[1].di;
westDivision.forEach(function (subdivision)
{
subdivision.t.forEach(function (team)
{
westTeams.push({
city: team.tc,
name: team.tn,
seed: team.see
});
});
});
function compare(a,b) {
if (a.see < b.see)
return -1;
if (a.see > b.see)
return 1;
return 0;
}
var sorted = westTeams.sort(compare);
sorted.forEach(function (el,i)
{
console.log(i+'. '+el.city+' '+el.name);
});
/**************************************************/
});//getJSON
});//ready
console output :
Portland Trail Blazers
Oklahoma City Thunder
Denver Nuggets
Utah Jazz
Minnesota Timberwolves
Golden State Warriors
Los Angeles Clippers
Phoenix Suns
Sacramento Kings
Los Angeles Lakers
Memphis Grizzlies
Houston Rockets
Dallas Mavericks
San Antonio Spurs
New Orleans Pelicans
I like to iterate with forEach. Rather then having to worry about indexes you can directly reference each item of the array.
Using this code you can put the data you want into an array.
//Get info above here
var westTeams = [];
var westDivision = info.sta.co[1].di;
westDivision.forEach(function (subdivision)
{
subdivision.t.forEach(function (team)
{
westTeams.push({
city: team.tc,
name: team.tn,
seed: team.see
});
});
});
Then you can sort them using obj.sort
function compare(a,b) {
if (a.seed < b.seed)
return -1;
if (a.seed > b.seed)
return 1;
return 0;
}
var sorted = westTeams.sort(compare);
Finally, you can print them in order.
sorted.forEach(function (el,i)
{
console.log((i+1)+'. '+el.city+' '+el.name);
});
Querying a large JavaScript object graph can be a tedious thing, especially if you want to have dynamic output. Implementing support for different filter criteria, sort orders, "top N" restrictions, paging can be difficult. And whatever you come up with tends to be inflexible.
To cover these cases you can (if you don't mind the learning curve) use linq.js (reference), a library that implements .NET's LINQ for JavaScript.
The following showcases what you can do with it. Long post, bear with me.
Preparation
Your NBA data object follows a parent-child hierarchy, but it misses a few essential things:
there are no parent references
the property that contains the children is called differently on every level (i.e. co, di, t)
In order to make the whole thing uniform (and therefore traversable), we first need to build a tree of nodes from it. A tree node would wrap objects from your input graph and would look like this:
{
obj: o, /* the original object, e.g. sta.co[1] */
parent: p, /* the parent tree node, e.g. the one that wraps sta */
children: [] /* array of tree nodes built from e.g. sta.co[1].di */
}
The building of this structure can be done recursively in one function:
function toNode(obj) {
var node = {
obj: obj,
parent: this === window ? null : this,
// we're interested in certain child arrays, either of:
children: obj.co || obj.di || obj.t || []
};
// recursive step (with reference to the parent node)
node.children = node.children.map(toNode, node);
// (*) explanation below
node.parents = Enumerable.Return(node.parent)
.CascadeDepthFirst("$ ? [$.parent] : []").TakeExceptLast(1);
return node;
}
(*) The node.parents property is a convenience facility. It contains an enumeration of all parent nodes except the last one (i.e. the root node, which is null). This enumeration can be used for filtering as shown below.
The result of this function is a nice-and-uniform interlinked tree of nodes. (Expand the code snippet, but unfortunately it currently does not run due to same-origin browser restrictions. Maybe there is something in the NBA REST API that needs to be turned on first.)
function toNode(obj) {
var node = {
obj: obj,
parent: this === window ? null : this,
children: obj.co || obj.di || obj.t || []
};
node.children = node.children.map(toNode, node);
node.parents = Enumerable.Return(node.parent)
.CascadeDepthFirst("$ ? [$.parent] : []").TakeExceptLast(1);
return node;
}
$(function () {
var standingsUrl = 'http://data.nba.com/data/v2014/json/mobile_teams/nba/2014/00_standings.json';
$.getJSON(standingsUrl, function(result) {
var sta = toNode(result.sta);
console.log(sta);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Result
Now that we have a fully traversable tree of nodes, we can use LINQ queries to do complex things with only a few lines of code:
// first build our enumerable stats tree
var stats = Enumerable.Return(toNode(result.sta));
// then traverse into children; the ones with a tid are teams
var teams = stats.CascadeDepthFirst("$.children")
.Where("$.obj.tid");
OK, we have identified all teams, so we can...
// ...select all that have a parent with val 'West' and order them by 'see'
var westernTeams = teams.Where(function (node) {
return node.parents.Any("$.obj.val === 'West'");
})
.OrderByDescending("$.obj.see");
// ...insert the top 5 into our page as list items
westernTeams.Take(5).Do(function (node) {
$("<li></li>", {text: node.obj.tc + ' ' + node.obj.tn}).appendTo("#topFiveList");
});
// ...turn them as an array of names
var names = westernTeams.Select("$.obj.tc + ' ' + $.obj.tn").ToArray();
console.log(names);
Of course what I have done there in several steps could be done in one:
// request details for all Northwest and Southeast teams who have won more than one game (*)
var httpRequests = Enumerable.Return(toNode(result.sta))
.CascadeDepthFirst("$.children")
.Where("$.obj.tid")
.Where(function (node) {
var str = node.obj.str.split(" ");
return str[0] === "W" && str[1] > 1 &&
node.parents.Any("$.obj.val==='Northwest' || $.obj.val==='Southeast'");
})
.Select(function (node) {
return $.getJSON(detailsUrl, {tid: node.obj.tid});
})
.ToArray();
$.when.apply($, httpRequests).done(function () {
var results = [].slice.call(arguments);
// all detail requests have been fetched, do sth. with the results
});
(*) correct me if I'm wrong, I have no idea what the data in the JSON file actually means
I've looked around for an answer, but I think this is a kind of weird question. How would I convert, as a text file using tabs for spacing, this:
parent
child
child
parent
child
grandchild
grandhcild
to
{
"name" : "parent",
"children" : [
{"name" : "child"},
{"name" : "child"},
]
},
{
"name" : "parent",
"children" : [
{
"name" : "child",
"children" : [
{"name" : "grandchild"},
{"name" : "grandchild"},
{"name" : "grandchild"},
]
},
]
}
JSON probably isn't perfect, but hopefully makes my point clear.
i've had the same problem. Here is the solution:
function node(title,lvl){
var children = [],
parent = null;
return {
title:title,
children:children,
lvl:()=>lvl==undefined?-1:lvl,
parent:()=>parent, //as a function to prevent circular reference when parse to JSON
setParent:p=>{parent=p},
appendChildren: function(c){
children.push(c);
c.setParent(this);
return this
},
}
}
function append_rec(prev,curr) {
if(typeof(curr)=='string'){ //in the recursive call it's a object
curr = curr.split(' ');//or tab (\t)
curr = node(curr.pop(),curr.length);
}
if(curr.lvl()>prev.lvl()){//curr is prev's child
prev.appendChildren(curr);
}else if(curr.lvl()<prev.lvl()){
append_rec(prev.parent(),curr) //recursive call to find the right parent level
}else{//curr is prev's sibling
prev.parent().appendChildren(curr);
}
return curr;
}
root = node('root');
var txt =
`parent
child
child
parent
child
grandchild
grandhcild`;
txt.toString().split('\n').reduce(append_rec,root);
console.log(JSON.stringify(root.children,null,3));
I've just implemented this feature for the tabdown markup language — it does exactly what you sought for.
https://github.com/antyakushev/tabdown
Usage is pretty simple:
var lines = data.toString().split('\n');
var tree = tabdown.parse(lines);
console.log(tree.toString());
You can also use the parse function outside of node.js, it does not depend on any modules.
This is my regex-based, recursion-free approach. It looks a bit "hacky" but makes perfect sense, you can try each step on regexr if you want to. It's written purposely verbose and can probably be compressed a bit. Also, this code assumes your text is tab-indented and only has one "parent", but you should be able to easily replace your indents and add a single "root" parent beforehand.
const string = `
parent
child
grandchild
child
child
grandchild
grandchild
`;
let json = string
.replace(
/(?:(\t+)(\S+)(?=(?:\n(?:(?:(?!\1))|(?:\1\S)))|$))/g,
"$1{\n$1\t\"name\": \"$2\",\n$1\t\"children\": []\n$1},"
) // this one replaces all empty nodes with a simple object with an empty children array
.replace(
/(?<=(^\t*))([^\s{]+)$\n(?=\1\t)/gm,
"{\"name\": \"$2\",\"children\": [\n"
); // this one replaces every immediate parent with an object and a starting children array
const lines = string.split("\n");
const maxDepth = Math.max(
...lines.map(line => line.replace(/[^\t]/g, "").length)
);
// this one basically closes all square brackets and curly braces
// this is a loop because it depends on the max depth of your source text and i also don't like recursion
for (let index = 0; index < maxDepth - 1; index++) {
json = json.replace(
/(^\t+)(.*,)("children": \[)((\n\1\t+[^\t\n]+)+)/gm,
"$1$2\n$1$3$4\n$1]},"
)
}
// this closes the root object brackets and removes trailing commas and newlines
json = `${json}\n]}`.replace(/,(?=\s*\])/g, "").replace(/\n/g, "");
const object = JSON.parse(json);
const betterLookingJson = JSON.stringify(object, null, "\t");
console.log(object);
console.log(betterLookingJson);
Generate JSON from Tab Tree Text File
The links below attack your problem specifically. All you need to do is update the code so the output is formatted to your requirements.
Python file parsing: Build tree from text file
Creating a tree/deeply nested dict from an indented text file in python
Parse Tree - Ruby example for parsing a tab tree. Created for #ruby on FreeNode.
Tab Delimiter to JSON
Converting Tab Delimited Textfile to JSON - A Python solution you could adapt.
Convert CSV To JSON - Online converter with many options.
Mr. Data Converter - Not an exact solution but you could adapt the code (fork on GitHub).
Other Help
How convert tsv to Json
Convert comma separated list into JSON using Javascript
I'm using Backbone.js/Underscore.js to render a HTML table which filters as you type into a textbox. In this case it's a basic telephone directory.
The content for the table comes from a Collection populated by a JSON file.
A basic example of the JSON file is below:
[{
"Name":"Sales and Services",
"Department":"Small Business",
"Extension":"45446",
},
{
"Name":"Technical Support",
"Department":"Small Business",
"Extension":"18800",
},
{
"Name":"Research and Development",
"Department":"Mid Market",
"Extension":"75752",
}]
I convert the text box value to lower case and then pass it's value along with the Collection to this function, I then assign the returned value to a new Collection and use that to re-render the page.
filterTable = function(collection, filterValue) {
var filteredCollection;
if (filterValue === "") {
return collection.toJSON();
}
return filteredCollection = collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
value = (!isNaN(value) ? value.toString() : value.toLowerCase());
return value.indexOf(filterValue) >= 0;
});
});
};
The trouble is that the function is literal. To find the "Sales and Services" department from my example I'd have to type exactly that, or maybe just "Sales" or "Services". I couldn't type "sal serv" and still find it which is what I want to be able to do.
I've already written some javascript that seems pretty reliable at dividing up the text into an array of Words (now updated to code in use).
toWords = function(text) {
text = text.toLowerCase();
text = text.replace(/[^A-Za-z_0-9#.]/g, ' ');
text = text.replace(/[\s]+/g, ' ').replace(/\s\s*$/, '');
text = text.split(new RegExp("\\s+"));
var newsplit = [];
for (var index in text) {
if (text[index]) {
newsplit.push(text[index]);
};
};
text = newsplit;
return text;
};
I want to loop through each word in the "split" array and check to see if each word exists in one of the key/values. As long as all words exist then it would pass the truth iterator and get added to the Collection and rendered in the table.
So in my example if I typed "sal serv" it would find that both of those strings exist within the Name of the first item and it would be returned.
However if I typed "sales business" this would not be returned as although both the values do appear in that item, the same two words do not exist in the Name section.
I'm just not sure how to write this in Backbone/Underscore, or even if this is the best way to do it. I looked at the documentation and wasn't sure what function would be easiest.
I hope this makes sense. I'm a little new to Javascript and I realise I've dived into the deep-end but learning is the fun part ;-)
I can provide more code or maybe a JSFiddle if needed.
Using underscore's any and all make this relatively easy. Here's the gist of it:
var toWords = function(text) {
//Do any fancy cleanup and split to words
//I'm just doing a simple split by spaces.
return text.toLowerCase().split(/\s+/);
};
var partialMatch = function(original, fragment) {
//get the words of each input string
var origWords = toWords(original + ""), //force to string
fragWords = toWords(fragment);
//if all words in the fragment match any of the original words,
//returns true, otherwise false
return _.all(fragWords, function(frag) {
return _.any(origWords, function(orig) {
return orig && orig.indexOf(frag) >= 0;
});
});
};
//here's your original filterTable function slightly simplified
var filterTable = function(collection, filterValue) {
if (filterValue === "") {
return collection.toJSON();
}
return collection.filter(function(data) {
return _.some(_.values(data.toJSON()), function(value) {
return partialMatch(value, filterValue);
});
});
};
Note: This method is computationally pretty inefficient, as it involves first looping over all the items in the collection, then all the fields of each item, then all words in that item value. In addition there are a few nested functions declared inside loops, so the memory footprint is not optimal. If you have a small set of data, that should be OK, but if needed, there's a number of optimizations that can be done. I might come back later and edit this a bit, if I have time.
/code samples not tested
I've spent all morning messing with this now and reading on here, but have found myself going round in circles!
I am trying to draw a chart using the excellent AmCharts Javascript Charts, to show me stock holding as a bar chart and stock turn as a line chart.
I cannot get both sets of data from one query to my database, and cannot use AmCharts StockChart as it is not time based data... therefore, I have two sets of data which need combining with Javascript.
The data is being pulled from a database and returned successfully as JSON arrays similar to this:
SALES DATA:
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}]
STOCK DATA:
[{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}]
Obviously the actual figures are made up in that example!
Now, what I need to do is to combine those to create this:
COMBINED DATA
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"975"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"1234"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
What we have there is the Sales Dataset combined with Stock Dataset to add the additional data of stockValue added to the corresponding brandName record.
I have tried using $.extend but I can't figure out how to use it in this situation.
It is perhaps important to note that the data pairs might not necessarily be in the right order, and it is possible, though unlikely, that there might not be a match, so some kind of zeroing error catching must be implemented.
What you'll need to do first is transform the data into two objects, whose properties are the values you want to merge together:
{
"Fender" : {"gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
"Gibson" : {"gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
"Epiphone" : {"gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}
}
and
{
"Gibson": {"stockValue":"1234"},
"Fender": { "stockValue":"975"},
"Epiphone": { "stockValue":"834"}
}
Once the transformation is done, you'll have two objects that you can merge using $.extend or other functions.
Update
For large sets, this gives results in nearly linear time:
var salesa = {}, stocka = {};
$.each(sales, function(i, e) {
salesa[e.brandName] = e;
});
$.each(stock, function(i, e) {
stocka[e.brandName] = e;
});
var combine = {};
$.extend(true, combine, salesa, stocka)
More speed can be tweaked if the merging happened during the second transformation callback ($each(stock...) instead of a separate call to $.extend() but it loses some of its obviousness.
I think what's he's trying to do is join the two datasets as if they were tables, joining by the brandName. From what I've been testing jQuery's $.extend() function does not take care of that, but merges objects according to their index in the Object arrays that it receives.
I think the matching of the key would need to be done manually.
stock = [{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}];
value = [{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}];
var results = [];
$(stock).each(function(){
datum1 = this;
$(value).each(function() {
datum2 = this;
if(datum1.brandName == datum2.brandName)
results.push($.extend({}, datum1, datum2));
});
});
Which would result in:
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"975"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"1234"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
Instead of what the use of $.extend() returns:
[{"brandName":"Gibson","gearShiftedPerMonth":"35","retailSalesPerMonth":"55","stockValue":"1234"},
{"brandName":"Fender","gearShiftedPerMonth":"23","retailSalesPerMonth":"43","stockValue":"975"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13","stockValue":"834"}]
If your example code reflects reality, then jQuery's $.extend will be the wrong tool for this.
It blindly copies data from one object to another. Notice that the order of your data is not consistent. The SALES DATA has Fender first, while the STOCK DATA has gibson first.
So jQuery's $.extend is mixing the two results. The "gearShifted" and "retailSales" for Fender is ending up with the "brandName" and "stockValue" for Gibson.
What you'll need is to iterate one array, and look up the "brandName" in the other, and then copy over the data you want. You could use $.extend for that part of it if you like...
var sales_data =
[{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}]
var stock_data =
[{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}]
var combined = $.map(sales_data, function(obj, i) {
return $.extend({}, obj, $.grep(stock_data, function(stock_obj) {
return obj.brandName === stock_obj.brandName
})[0]);
});
Note that this is not terribly efficient, but unless the data set is enormous, it shouldn't be an issue.
DEMO: http://jsfiddle.net/sDyKx/
RESULT:
[
{
"brandName": "Fender",
"gearShiftedPerMonth": "35",
"retailSalesPerMonth": "55",
"stockValue": "975"
},
{
"brandName": "Gibson",
"gearShiftedPerMonth": "23",
"retailSalesPerMonth": "43",
"stockValue": "1234"
},
{
"brandName": "Epiphone",
"gearShiftedPerMonth": "10",
"retailSalesPerMonth": "13",
"stockValue": "834"
}
]
In vanilla javascript you can do:
var sales = [{"brandName":"Fender","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Gibson","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Epiphone","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}];
var stock = [{"brandName":"Gibson","stockValue":"1234"},
{"brandName":"Fender","stockValue":"975"},
{"brandName":"Epiphone","stockValue":"834"}];
var combined = stock.slice(0);
for (var i = 0; i < stock.length; i++) {
for (var j = 0; j < sales.length; j++) {
if (stock[i].brandName === sales[j].brandName) {
for (var attrname in sales[j]) { combined[i][attrname] = sales[j][attrname]; }
}
}
}
JSON.stringify(combined)
produces
[
{"brandName":"Gibson","stockValue":"1234","gearShiftedPerMonth":"23","retailSalesPerMonth":"43"},
{"brandName":"Fender","stockValue":"975","gearShiftedPerMonth":"35","retailSalesPerMonth":"55"},
{"brandName":"Epiphone","stockValue":"834","gearShiftedPerMonth":"10","retailSalesPerMonth":"13"}
]
I have the following JSON response from a ajax-request.
var json = {
"response": {
"freeOfChargeProduct": {
"description": "Product",
"orderQty": 5,
"productName": "XYZ",
"qty": 6,
"details": {
"price": 55.5,
"instock": "true",
"focQuantity": 1
}
},
"orderLineId": 4788,
"totalOrderLinePrice": "741.36",
"totalOrderPrice": "1,314.92",
"totalQty": 17
};
The JSON dosen't always return a "freeOfChargeProduct" property. So if I want to get the "freeOfChargeProduct" price, then I have to do the following:
var getFreeOfChargeProductPrice = function() {
var r = json.response;
if (r && r.freeOfChargeProduct && r.freeOfChargeProduct.details) {
return r.freeOfChargeProduct.details.price;
}
return null;
};
No problems. But it's very annoying to check every property in the object, so I created a function that check if a property in a object is defined.
var getValue = function (str, context) {
var scope = context || window,
properties = str.split('.'), i;
for(i = 0; i < properties.length; i++) {
if (!scope[properties[i]]) {
return null;
}
scope = scope[properties[i]];
}
return scope;
};
var price = getValue('json.response.freeOfChargeProduct.details.price');
// Price is null if no such object exists.
Now to my question: Is this a good or bad way to check if a property exists in an object? Any better suggestions/methods?
EDIT:
I don't wan't to use the &&-operator. I am lazy and I'm looking for a reusable method to check if a object (or property of a object) is defined.
:) Thanks!
Use the guard pattern:
if (json.response && json.response.freeOfChargeProduct && json.response.freeOfChargeProduct.details) {
// you can safely access the price
}
This is how the guard pattern works.
if (a && a.b && a.b.c) { ... } else { ... }
The first check is "Does the property a exist?". If not, the else-branch gets executed. If yes, then the next check occurs, which is "Does object a contain the property b?". If no, the else-branch executes. If yes, the final check occurs: "Does the object a.b contain the property c?". If no, the else-branch executes. If yes (and only then), the if-branch executes.
Update: Why is it called "guard pattern"?
var value = a && b;
In this example, the member b (the right operand) is guarded by the && operator. Only if the member a (the left operand) is truthy ("worthy"), only then the member b is returned. If, however, the member a is falsy ("not worthy"), then it itself is returned.
BTW, members are falsy if they return these values: null, undefined, 0, "", false, NaN. Members are truthy in all other cases.
if(x && typeof x.y != 'undefined') {
...
}
// or better
function isDefined(x) {
var undefined;
return x !== undefined;
}
if(x && isDefined(x.y)) {
...
}
This will work for any data type in JavaScript, even a number that is zero. If you are checking for an object or string, just use x && x.y within the if statement, or if you already know that x is an object, if(x.y) ...
You could do something like this:
try{
var focp = json.response.freeOfChargeProduct
var text = "You get " + focp.qty + " of " +
focp.productName +
" for only $" + (focp.qty-focp.details.focQuantity)*focp.details.price +
", You save $" + focp.details.focQuantity*focp.details.price;
$("order_info").innerText = text;
} catch(e) {
// woops, handle error...
}
It would generate a message like this from the provided data in your question if the fields exists:
You get 6 of XYZ for only $277,5, You save $55.5
If the data is non-existing, you'll end up in the catch block. You could always just to a Try, Catch, Forget here if you can't come up with a way to handle the error (Maybe do a new AJAX request for the data?).
This is not a syntax issue as it is a design pattern issue.
Question A.
* Do you have control of the json server?
If the answer to this is no, which I assume, the situation will be all on the client.
Please read this:
http://martinfowler.com/eaaDev/PresentationModel.html
As the server is the source, in this case it will provide the model.
This pattern specifies an additional artifact: The presentation model (PM). In javascript i would suggest two artifacts, a additional for the convertor code.
According to this design pattern the PM is responsible for converting the model to the PM, and back again if necessary. In your case no conversion from PM to M will ever occur.
This means that a js object has a method or constructor that digest the model and translate itself, with the help of the convertor (below).
Doing this you will end up with a PM looking like this:
var OrderlinePM = {
"hasFreeOfCharge": false | true,
"freeOfCharge" : {...}
`enter code here`
this.getFreeOfCharge = function() {
...
}
this.fromModel = function(jsonEntry, convertor) {
//convert this with the convertor ;) to a for this specific view usable OrderlinePM
// also inwith
...
}
enter code here
"orderLineId":0,
"totalOrderLinePrice":"741.36",
"totalOrderPrice":"1,314.92",
"totalQty":17
};
function mySpecialFunctionPMConvertor {
this.fromModel = function() {
... //do strange stuff with the model and poulate a PM with it.
}
}
Ok, I give up trying to format code in this rich text editor :(
You can have several PM:s for diffrent tasks all depending on the same model object.
In addition this will make the converter object testable in something that could be automatically executed.... err ok maby manually, but anyway.
So the problem of the cumbersome reflection code is really not a problem. But cohesion is a issue, expessially in JavaScript.