How to merge few 3D JSON objects in JS [or jQuery] - javascript

I have to merge 2 (up to 6) JSON objects.
I got this code: http://jsfiddle.net/5Uz27/
But with this code, I can only merge the first level of the objects, so the deeper levels are usually overwritten. Check the output.
How can I fix that?

jQuery.extend(true, original_object, extend_with);
source: http://api.jquery.com/jQuery.extend/

With jQuery, you can use $.extend() to do a "deep"/recursive merge of objects, by passing in true as the first argument.
Here's how this might work in your example:
// turn the strings into objects
var pref_array = $.map(json_holder, JSON.parse);
// add the deep=true argument
pref_array.unshift(true);
// now do a deep extend, passing the array as arguments
var prefs = $.extend.apply(null, pref_array );
This might be a little obtuse (you could make it even more so, but tighter, by setting pref_array to [true].concat($.map(json_holder, JSON.parse))), but it avoids the ungainly for loop (that might be personal preference, I suppose).
Working jsFiddle here: http://jsfiddle.net/e6bnU/

Related

Why doesn't this recursive jQuery.extend call work?

I have this javascript code, using jquery.extend:
hash1 = {"/":{"programme-nav":true}};
hash2 = {"/":{"what-is-scheme":true}};
merged = $.extend(hash1, hash2);
deep_merged = $.extend(true, hash1, hash2);
//expected_deep_merged = {"/":{"programme-nav":true, "what-is-scheme":true}};
console.log("merged = "+JSON.stringify(merged));
// gives {"/":{"what-is-scheme":true}};
console.log("deep_merged = "+JSON.stringify(deep_merged));
// also gives {"/":{"what-is-scheme":true}};
According to the API (https://api.jquery.com/jquery.extend/), passing true as the first argument will recursively merge the objects. In this case, i'd expect to get the result in the comment. But, i get the same as when i do the non-deep extend.
I've set up a jsfiddle for this here which outputs to the console, https://jsfiddle.net/0725apqn/2/, so you can have a play.
Can anyone explain where i'm going wrong? thanks, Max
EDIT: I am an idiot. Passing true as the first argument DOES do the deep merge correctly, but what i didn't notice was that i'd already overwritten hash1 in the first part of my example (where i do the non-deep merge), so that when i came to do the "deep" merge, i was actually doing this:
deep_merged = $.extend(true, {"/":{"what-is-scheme":true}}, {"/":{"what-is-scheme":true}});
Massive "doh". thanks anyone who read this. I'm going to award as many points as possible and then delete this question.
Extend does not make a copy of your data structure, it modifies the one passed as first argument - which is returned by the method.
Tour first call to entend modified 'hash1', which ends up being the same as 'hash2'.
On the second call you are merging two identical hashes.
To achieve what you are aiming for, pass a new instance in the extend parameters.
result = $.extend(true, {}, hash1, hash2);
This call will not modify 'hash1'

Array.prototype.map() and Array.prototype.forEach()

I've an array (example array below) -
a = [{"name":"age","value":31},
{"name":"height (inches)","value":62},
{"name":"location","value":"Boston, MA"},
{"name":"gender","value":"male"}];
I want to iterate through this array of objects and produce a new Object (not specifically reduce).
I've these two approaches -
a = [{"name":"age","value":31},
{"name":"height (inches)","value":62},
{"name":"location","value":"Boston, MA"},
{"name":"gender","value":"male"}];
// using Array.prototype.map()
b = a.map(function(item){
var res = {};
res[item.name] = item.value;
return res;
});
console.log(JSON.stringify(b));
var newObj = [];
// using Array.prototype.forEach()
a.forEach(function(d){
var obj = {};
obj[d.name] = d.value;
newObj.push(obj)
});
console.log(JSON.stringify(newObj))
Is it not right to just use either one for this sort of operations?
Also, I'd like to understand the use case scenarios where one will be preferred over the other? Or should I just stick to for-loop?
As you've already discussed in the comments, there's no outright wrong answer here. Aside from some rather fine points of performance, this is a style question. The problem you are solving can be solved with a for loop, .forEach(), .reduce(), or .map().
I list them in that order deliberately, because each one of them could be re-implemented using anything earlier in the list. You can use .reduce() to duplicate .map(), for instance, but not the reverse.
In your particular case, unless micro-optimizations are vital to your domain, I'd make the decision on the basis of readability and code-maintenance. On that basis, .map() does specifically and precisely what you're after; someone reading your code will see it and know you're consuming an array to produce another array. You could accomplish that with .forEach() or .reduce(), but because those are capable of being used for more things, someone has to take that extra moment to understand what you ARE using them for. .map() is the call that's most expressive of your intent.
(Yes, that means in essence prioritizing efficiency-of-understanding over efficiency-of-execution. If the code isn't part of a performance bottleneck in a high-demand application, I think that's appropriate.)
You asked about scenarios where another might be preferred. In this case, .map() works because you're outputting an array, and your output array has the same length as your input array. (Again; that's what .map() does). If you wanted to output an array, but you might need to produce two (or zero) elements of output for a single element of input, .map() would be out and I'd probably use .reduce(). (Chaining .filter().map() would also be a possibility for the 'skip some input elements' case, and would be pretty legible)
If you wanted to split the contents of the input array into multiple output arrays, you could do that with .reduce() (by encapsulating all of them as properties of a single object), but .forEach() or the for loop would look more natural to me.
First, either of those will work and with your example there's no reason not to use which ever is more comfortable for your development cycle. I would probably use map since that is what is for; to create "a new array with the results of calling a provided function on every element in this array."
However, are you asking which is the absolute fastest? Then neither of those; the fastest by 2.5-3x will be a simple for-loop (see http://jsperf.com/loop-vs-map-vs-foreach for a simple comparison):
var newObj = [];
for (var i = 0, item; item = a[i]; i++) {
var obj = {};
obj[item.name] = item.value;
newObj.push(obj);
});
console.log(JSON.stringify(newObj));

Joining values in an Javascript array

Say I have:
var Certificated = {}
Sub items are added dynamically and variate. Possible outcome:
var Certificated = {
Elementary: ["foo","bar", "ball"]
MiddleSchool: ["bar", "crampapydime"]
};
I want to do the following:
Certificated.Elementary = Certificated.Elementary.join("");
Except I need it to do that on all of the objects inside.
Keep in mind I can't know for sure the titles of nor how many objects will be inside Certificated.
My question is how can I use .join("") on all elements inside Certificated, without calling each one specifically?
EDIT: I am aware .join() is for arrays and the objects inside Certificated are going to be arrays. Therefore the join method.
Does this work?
for (var key in Certificated) {
if (Certificated.hasOwnProperty(key)) {
Certificated[key] = Certificated[key].join("");
}
}
It loops through all properties of Certificated, and makes a quick safe check for the key being a real property, then uses bracket notation - [""] - to do your join.
Quick question - are you sure you want to use join? I know you just provided an example, but you can't call join on a string...it's for arrays. Just wanted to make sure you knew.
Here's a jsFiddle of my code working with arrays being used for the properties:
http://jsfiddle.net/v48dL/
Notice in the browser console, the properties' values are strings because the join combined them with "".

How do I sort a JSON object by a nested value?

I have an ajax call that returns a JSON object that is pretty complex and I'm having a hard time sorting it.
My call:
$.post('/reports-ajax',arguments, function(data) {}
The response:
{
"10001":{
"unitname":"Fort Worth",
"discounts":{"12-02-2012":"34.810000","12-03-2012":"20.810000","12-04-2012":"27.040000"},
"gross":{"12-02-2012":"56.730000","12-03-2012":"19.350000","12-04-2012":"66.390000"},
"net":{"12-02-2012":"61.920000","12-03-2012":"98.540000","12-04-2012":"39.350000"},
"discounts_total":82.66,
"gross_total":82.47,
"net_total":99.81,
"number":10001
},
"10002":{
"unitname":"Dallast",
"discounts":{"12-02-2012":"12.600000","12-03-2012":"25.780000","12-04-2012":"47.780000","12-05-2012":"45.210000"},
"gross":{"12-02-2012":"29.370000","12-03-2012":"91.110000","12-04-2012":"60.890000","12-05-2012":"51.870000"},
"net":{"12-02-2012":"16.770000","12-03-2012":"65.330000","12-04-2012":"13.110000","12-05-2012":"06.660000"},
"discounts_total":131.37,
"gross_total":33.24,
"net_total":101.87,
"number":10002
},
"32402":{
"unitname":"Austin",
"discounts":{"12-05-2012":"52.890000","12-02-2012":"22.430000","12-03-2012":"58.420000","12-04-2012":"53.130000"},
"gross":{"12-05-2012":"25.020000","12-02-2012":"2836.010000","12-03-2012":"54.740000","12-04-2012":"45.330000"},
"net":{"12-04-2012":"92.200000","12-05-2012":"72.130000","12-02-2012":"13.580000","12-03-2012":"96.320000"},
"discounts_total":186.87,
"gross_total":161.1,
"net_total":174.23,
"number":32402
}
}
I go over the function with a standard each call and do some awesome stuff with highcharts but now I'm trying to sort the responses by the net_total call and I can't figure it out.
I tried .sort() and it errors out that its not a function. I've been reading for a while but guess I'm not finding the right results. This looked promising: Sorting an array of JavaScript objects but it failed with the .sort is not a function. It seems most .sort are on [] arrays not full objects..
Any help would be greatly appreciated.
Sorting objects doesn't make sense since object keys have no positional value. For example, this:
{ a:1, b:2 }
and this:
{ b:2, a:1 }
are exactly the same object. They're not just similar, they're the same.
Nothing in javascript per se gives object keys any positional value. Some people perhaps are mistaken in the belief that:
for (var key in obj) {
iterates through the object keys in a specific sequence. But this is wrong. You should always assume that the for .. in loop processes object keys in random order, always, all the time.
Obviously, if you're going to write a web browser, you're not going to implement a random number generator to parse a for .. in loop. Therefore most web browsers have an accidental stability to how the for .. in loop processes object keys.
Developers who learn javascript by playing around with the browser may figure out that their browser iterates through objects in alphabetical order for example, or the order the keys were added to the object. But this is totally accidental and cannot be relied upon. The browser vendor may change this behavior in the future without violating any backwards compatability (except with buggy scripts written by people who believe objects have a sort order). Not to mention that different browsers have different implementations of javascript and therefore not necessarily have the same internal key ordering of objects.
All the above is besides the point. "Key sort order" does not make any sense in javascript and any behavior observed is merely implementation detail. In short, javascript object does not have key order, just assume it's random.
Solution
Now, what you're really trying to do is not sort the object (you can't, it doesn't make sense). What you're really trying to do is process the object attributes in a specific order. The solution is to simply create an array (which has sorting order) of object keys and then process the object using that array:
// First create the array of keys/net_total so that we can sort it:
var sort_array = [];
for (var key in Response) {
sort_array.push({key:key,net_total:Response[key].net_total});
}
// Now sort it:
sort_array.sort(function(x,y){return x.net_total - y.net_total});
// Now process that object with it:
for (var i=0;i<sort_array.length;i++) {
var item = Response[sort_array[i].key];
// now do stuff with each item
}
What you have there isn't an array and has no order, so you'll have to transform it into an array so you can give it order.
Vaguely:
var array = [];
$.each(data, function(key, value) {
array.push(value);
});
array.sort(function(a, b) {
return a.net_total - b.net_total;
});
Live Example | Source
As GolezTroi points out in the comments, normally the above would lose the key that each entry is stored under in data and so you'd add it back in the first $.each loop above, but in this case the entries already have the key on them (as number), so there's no need.
Or you can replace the first $.each with $.map:
var array = $.map(data, function(entry) {
return entry;
});
// ...and then sort etc.
...whichever you prefer.

Change the value of an array changes original array JavaScript

The following code causes both elements from id 0 to be set to -, even though I want only one to be set to -1. Am I just creating a reference to the labelArray, or is something else?
labelArray.sort(compare);
valueArray = labelArray;
valueArray[0] = '-1';
labelArray[0] = '-';
All help is appreciated.
UPDATE (2019): It's been several years since I first did this post, and ES6 is used pretty much universally. So, I wanted to come back and add that, instead of using the slice() method recommended in the accepted answer, you can instead use array destructing in the following to make a copy:
valueArray = [...labelArray];
Yes. Both valueArray and labelArray reference the same underlying array. To make a copy, use slice():
valueArray = labelArray.slice(0);
NOTE: Slice() only copies 1 level deep, which works fine for primitive arrays. If the array contains complex objects, use something like jQuery's clone(), credit #Jonathan.
Am I just creating a reference to the labelArray […] ?
Yes, exactly. valueArray and labelArray still identify the same object, which hasn't been copied.
valueArray is just a reference to labelArray.
What you want to do is clone the array. You can do this using jQuery.clone() or a similar cloning function.

Categories

Resources