0

I start with multi-dimensional array like so:

[
    ['prop1', 'prop2', 'prop3', 'prop3'],
    ['val1', 'val2', 'val3', 'val4']
]

I need to transform this in an object like this one:

{ prop1: 'val1',
  prop2: 'val2',
  prop3: ['val3', 'val4']
}

Normally I could use _.object(array[0], array[1]) to solve this but the presence of the duplicate key prop3 complicates things quite a bit;

Actual _.object will set prop3: 'val4', erasing val3

Anyone got an idea on how I could achieve the results presented above?

Thank you

3
  • 1
    Will there always be 2 items in the array or can there be more? Commented Apr 3, 2014 at 18:51
  • Are those duplicate property names always adjacent to each other? Can the values be arrays as well? Commented Apr 3, 2014 at 19:00
  • To answer both questions: - There can be more than 1 duplicate per key - And no they won't necessarily be adjacent in the array --- PS: can't we put linebreaks at least in these commments? can't seem to format this Commented Apr 3, 2014 at 19:34

3 Answers 3

1

I'd probably do something like this, which would be easy enough to turn into an underscore mixin:

var arr = [
    ['prop1', 'prop2', 'prop3', 'prop3'],
    ['val1', 'val2', 'val3', 'val4']
],
    keys = arr[0],
    vals = arr[1],
    obj = {};

keys.forEach( function( name, index )
{
    if ( !Object.prototype.hasOwnProperty.call(obj, name) )
    {
        obj[name] = vals[index];
    }
    else
    {
        if ( obj[name] instanceof Array )
        {
            obj[name].push( vals[index] );
        }
        else
        {
            var val = obj[name];
            obj[name] = [val, vals[index]];
        }
    }
} );

Example in a fiddle. (turned into a mixin)

jsPerf comparison with underscore-based code.

Sign up to request clarification or add additional context in comments.

7 Comments

!obj[name] is insufficient. What if the obj[name] = false or any non truthy value
Also typeof x == 'array' is not a thing
FWIW, the other example using Underscore methods almost exclusively clocks in at 18442.733ms, while using only the _.each() method in my answer above takes 5.314ms. If I take that out and use keys.forEach() instead, it brings the time down to 0.118ms. Point being, if you can avoid using a library, it's faster. :)
obj[name].isArray is not a thing. You want Array.isArray(obj[name]). Note: many of these features require ES5
jsfiddle.net/y6ttk/4 reflects proper checking of an array as well as turning it into a mixin, if you'd rather write one line (and have to use this over and over again), @OlivierClément.
|
1

Simply zipping the arrays and then using a custom reduce should do the trick. Should be pretty simple to adapt this if your data arr can contain more than 2 collections.

_.mixin({
   asArray: function(value) {//simple helper from https://github.com/jashkenas/underscore/pull/1467
     if (value == null) return [];
     if (value.length === +value.length && !_.isString(value)) return _.toArray(value);
     return [value];
   }
});


var arr = [
    ['prop1', 'prop2', 'prop3', 'prop3'],
    ['val1', 'val2', 'val3', 'val4']
];
/* ... */
_.chain(arr)
 .zip()
 .reduce(function(memo, propKey) {
   if(_.has(memo, propKey[0])) { // property already in the object. Make array if necessary and insert item at end
       (memo[propKey[0]] = _.asArray(memo[propKey[0]])).push(propKey[1]); //push value into object
   }
   else {
      memo[propKey[0]] = propKey[1];
   }
   return memo;
}, {})
 .value();

Produces: {"prop1":"val1","prop2":"val2","prop3":["val3","val4"]}

The asArray can be easily replaced if you don't want to mixin. See previous revision.

4 Comments

@jacobroufa if you want to benchmark correct your implementation and make a jsperf example
Revised my post to reflect your suggested changes and add a jsperf example comparison.
Hi, thanks for your answer - as it is, doesn't seem to do the trick; As is, it results in: Object {prop1,prop2,prop3,prop3: undefined, val1,val2,val3,val4: undefined} Without the zip: Object {prop1: "prop2", val1: "val2"} which isn't what I'm after either --- I am not sure why the zip would be needed too, as it seems to just wrap my arrays into more arrays...?
Whoops, sorry I broke the code between revisions ;). I used .zip to group your array into sub arrays of the form [key, value] for easy processing.
0

Just for completeness I wanted to mention this method:

http://lodash.com/docs#zipObject - or (new) _.object()

Creates an object composed from arrays of keys and values. Provide either a single two dimensional array, i.e. [[key1, value1], [key2, value2]] or two arrays, one of keys and one of corresponding values.

Example

_.zipObject(['fred', 'barney'], [30, 40]);
// → { 'fred': 30, 'barney': 40 }

Unfortunately, it doesn't combine several values into an array for one key:

_.object(['key1', 'key2'], [1,2]);
// { key1: 1, key2: 2 }
_.object(['key1', 'key1'], [1,2]);
// { key1: 2 }
_.object(['key1'], [1,2]);
// { key1: 1 }

So this is not really the solution to your answer but I think it's still valuable to take note of this function.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.