12

I have two or more javascript objects. I want to merge them adding values of common properties and then sort them in descending order of values.

e.g.

var a = {en : 5,fr: 3,in: 9}
var b = {en: 8,fr: 21,br: 8}

var c = merge(a,b)

c should then be like this:

c = {
fr: 24,
en: 13,
in:9,
br:8
} 

i.e. both objects are merge, values of common keys are added and then keys are sorted.

Here's what I've tried:

var a = {en : 5,fr: 3,in: 9}
var b = {en: 8,fr: 21,br: 8}
c = {}

// copy common values and all values of a to c
for(var k in a){
  if(typeof b[k] != 'undefined'){  
    c[k] = a[k] + b[k]  
  }
  else{ c[k] = a[k]}
}

// copy remaining values of b (which were not common)
for(var k in b){
 if(typeof c[k]== 'undefined'){
  c[k] = b[k]
 }
} 

// Create a object array for sorting
var arr = [];

for(var k in c){
 arr.push({lang:k,count:c[k]})
}

// Sort object array
arr.sort(function(a, b) {
   return b.count - a.count;
})

but I dont think its good. So many loops :( It would be nice if someone can provide a less messy and good code.

Jashwant
  • 28,410
  • 16
  • 70
  • 105

4 Answers4

3

In ES2015+, object properties are ordered (first by ascending numeric keys, then by insertion order for non-numeric keys). This is guaranteed by the specification if you use one of the methods for which iteration order is specified (like Object.getOwnPropertyNames).

In ES2020+, the methods for which enumeration order used to be unspecified are now specified (though environments have been following it for ages anyway).

But you have to be sure that none of the properties are numeric (otherwise, they'll come first, before non-numeric properties, no matter the insertion order).

Use reduce to iterate over each object and create or add to the same property on the accumulator. Then, sort the object's entries, and use Object.fromEntries to transform it into an object with sorted properties. No need for jQuery:

var a = {en : 5,fr: 3,in: 9}
var b = {en: 8,fr: 21,br: 8}
console.log(merge(a, b));

function merge(...objects) {
  const merged = objects.reduce((a, obj) => {
    Object.entries(obj).forEach(([key, val]) => {
      a[key] = (a[key] || 0) + val;
    });
    return a;
  }, {});
  return Object.fromEntries(
    Object.entries(merged).sort(
      (a, b) => b[1] - a[1]
    )
  );
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
2

It is not possible to sort the properties of an object, you can however sort an array:

var merged = $.extend({}, a);
for (var prop in b) {
    if (merged[prop]) merged[prop] += b[prop];
    else merged[prop] = b[prop];
}
// Returning merged at this point will give you a merged object with properties summed, but not ordered.
var properties = [];
for (var prop in merged) {
    properties.push({
        name: prop,
        value: merged[prop]
    });
}
return properties.sort(function(nvp1, nvp2) {
    return nvp1.value - nvp2.value;
});
Rich O'Kelly
  • 41,274
  • 9
  • 83
  • 114
  • I have changed foreach to for and name= prop to name:prop, but it still returns error :( – Jashwant Feb 07 '12 at 11:16
  • worked like charm :) I didnt get the code, but this is what I call less messy code. Whats happening here ? return nvp1.value == nvp2.value ? 0 : nvp1.value > nvp2.value ? 1: -1; – Jashwant Feb 07 '12 at 11:57
  • @Jashwant I've updated that line to be slightly simpler :). But in the above it uses the conditional operator `?` to check for equality of values, if equal it returns 0, otherwise if value1 is greater than value2 it returns 1 otherwise -1. – Rich O'Kelly Feb 07 '12 at 12:03
  • Your expectation was to get object but you are getting array with this solution, – Mujah Maskey Feb 07 '12 at 12:05
  • @PravatMaskey It is not possible to 'sort' properties of an object, you can however sort an array. – Rich O'Kelly Feb 07 '12 at 12:24
  • @rich.okelly , yes, that is what I'm telling. I forgot to check his requirment "sort" dictionary object. So I had deleted my answer :D. – Mujah Maskey Feb 07 '12 at 12:45
2

EDIT - i modified the script, this merges the properties if they are of the same type: numbers are summed, strings are concatenated and objects are recursively merged. I didn't include sorting because (quoting this answer Sorting JavaScript Object by property value)

JavaScript objects are unordered by definition (see the ECMAScript Language Specification, section 8.6). The language specification doesn't even guarantee that, if you iterate over the properties of an object twice in succession, they'll come out in the same order the second time.

If you need things to be ordered, use an array and the Array.prototype.sort method.

function is_object(mixed_var) {
    if (Object.prototype.toString.call(mixed_var) === '[object Array]') {
        return false;
    }
    return mixed_var !== null && typeof mixed_var == 'object';
}

function merge(a, b) {
    var cache = {};
    cache = unpackObject(a, cache);
    cache = unpackObject(b, cache);
    return cache;


}

function unpackObject(a, cache) {
    for (prop in a) {
        if (a.hasOwnProperty(prop)) {
            if (cache[prop] === undefined) {
                cache[prop] = a[prop];
            } else {
                if (typeof cache[prop] === typeof a[prop]) {
                    if (is_object(a[prop])) {
                        cache[prop] = merge(cache[prop], a[prop]);
                    } else {
                        cache[prop] += a[prop];
                    }
                }
            }
        }
    }
    return cache;
}

var a = {
    en: 5,
    fr: 3,
    in : 9,
    lang: "js",
    object: {nestedProp: 6}

}
var b = {
    en: 8,
    fr: 21,
    br: 8,
    lang: "en",
    object: {nestedProp: 1, unique: "myne"}
}

var c = merge(a, b);

fiddle here http://jsfiddle.net/vyFN8/1/

Community
  • 1
  • 1
Nicola Peluchetti
  • 76,206
  • 31
  • 145
  • 192
1

Here is my attempt, which is recursive for nested objects - https://gist.github.com/greenafrican/19bbed3d8baceb0a15fd

// Requires jQuery
// Merge nested objects and if the properties are numbers then add them together, else
// fallback to jQuery.extend() result

function mergeObjectsAdd(firstObject, secondObject) {
    var result = $.extend(true, {}, firstObject, secondObject);
    for (var k in result) {
        if ("object" === typeof result[k]) {
            firstObject[k] = firstObject[k] || {};
            secondObject[k] = secondObject[k] || {};
            result[k] = mergeObjectsAdd(firstObject[k], secondObject[k]);
        } else {
            firstObject[k] = firstObject[k] || 0;
            secondObject[k] = secondObject[k] || 0;
            result[k] = ("number" === typeof firstObject[k] && "number" === typeof secondObject[k]) ? (firstObject[k] + secondObject[k]) : result[k];
        }
    }
    return result;
}
greenafrican
  • 2,516
  • 5
  • 27
  • 38