1

Is there a more functional way to create an object in JavaScript programatically without assigning each key individually?

For example, given this array (imagine it comes from an outside data source):

let arr = ['a=1', 'b=2', 'c=3'];

What is an easy way to convert this to an object like so?

let expectedResult = { a: '1', b: '2', c: '3'};

It's clunky to assign a new object and loop over the elements with a for or foreach. It would be nice if there were something akin to map that could yield such a final result.

Imagine you could do this:

arr
  .map(item => new KeyValuePair(itemKey, itemValue)) // magically get itemKey/itemValue
  .toObjectFromKeyValuePairs();

That'd be it right there. But of course there's no such function built in.

6
  • Array#reduce is less clunky I guess Commented Jan 19, 2017 at 0:22
  • I'd like to see your implementation of that. Commented Jan 19, 2017 at 0:24
  • The for loop would probably be the most efficient code for this, if that's what matters to you. Are you asking for a functional way to do this? Commented Jan 19, 2017 at 0:25
  • I'd like to see your implementation of that - why? it's still a loop, technically, and you consider looping over the elements to be clunky Commented Jan 19, 2017 at 0:25
  • @JaromandaX I missed that there was an optional second parameter of Array.reduce. Now I see how it can be used. Commented Jan 19, 2017 at 0:27

4 Answers 4

3

If you're looking for a more functional approach to the code, you could use a library such as Lodash which makes code more succinct.

You could use _.fromPairs to convert pairs of data in arrays to key-value pairs of an object.

const convert = arr => _(arr)
    .map(s => _.split(s, '=', 2))
    .fromPairs()
    .value();
console.log(convert(['a=1', 'b=2', 'c=3']));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

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

16 Comments

+1 - Major endorsement for Lodash. Makes life in js land FAR easier, defensively protects against type issues, awesome useful functions, and much more...
Although I'd also encourage you to refactor arr.map to _.map and s.split to _.split, for strong "defensiveness" in your example...
@cale_b I agree. It took me a little bit to understand what you meant by "defensiveness"
Note that querystrings such as ?a or ?a=b=c may not work with your code.
@ErikE What would the desired output be for those inputs?
|
2

You could use reduce, split and slice:

var arr = ['a=1', 'b=2', 'c=3'];

var out = arr.reduce(
  function (output, input) {
    if (typeof input === 'string') {
      var key = input.split('=',1)[0];
      output[key] = input.slice( key.length + 1 );
    }
    return output;
  },
  {}
);

I use the second argument of split to make it stop after the first = found. Then using slice on the input (treating it as an array of characters) allows the value to contain the = separator as in the case of a=b=c.

By using slice, the value will always be a string, even if it is an empty one. If you want to have null values you could change the line to:

output[key || null] = input.slice( key.length + 1 ) || null;

The type check for string is present since split throws error on null and undefined.

If you wanted to parse the current page's query string for example, you could do it using the above technique just like this:

function getQueryStringParams() {
  var reEncodedSpace = /\+/g;
  return location.search.length > 1 // returns false if length is too short
    && location.search.slice( 1 ).split( '&' ).reduce(
      ( output, input ) => {
        if ( input.length ) {
          if ( output === false ) output = {};
          input = input.replace( reEncodedSpace, ' ' ); //transport decode
          let key = input.split( '=', 1 )[ 0 ]; // Get first section as string
          let value = decodeURIComponent( input.slice( key.length + 1) ); // rest is value
          key = decodeURIComponent( key ); // transport decode

          // The standard supports multiple values per key.
          // Using 'hasOwnProperty' to detect if key is pressent in output,
          // and using it from Object.prototype instead of the output object
          // to prevent a key of 'hasOwnProperty' to break the code.
          if ( Object.prototype.hasOwnProperty.call( output, key ) ) {
            if ( Array.isArray( output[ key ] ) ) {
              // Third or more values: add value to array
              output[ key ].push( value );
            } else {
              // Second value of key: convert to array.
              output[ key ] = [ output[ key ], value ];
            }
          } else {
            // First value of key: set value as string.
            output[ key ] = value;
          }
        }
        return output;
      },
      false
    );
}

The function returns false if the search is empty.

17 Comments

After getting the hint about reduce from Jaromanda X, I figured out something similar to this. Though, I see no reason to split AND slice, when you could split once and use [1].
@ErikE Well, if your input is 'a=b=c' you get the wrong result if you just use split.
I looked at using var pieces = input.split('='); then use pieces[0] and pieces[1] — it ends up with the ugly output[pieces[0]] but then doesn't have to call input.slice(...) ... thoughts?
Using .split('=', 2) would eliminate the need for slice while still being safe.
If the input is "a=b=c" you get {a:'b'} if you only use split (even if you use split('=',2)). With slice, as I have written it, you get {a:'b=c'}
|
0

If you're willing to spare having one additional line for declaration, this could work for you. Although using a library like lodash or underscore, as mentioned in other answers would certainly help:

var arr = ['a=1', 'b=2', 'c=3'];
var expectedResult = {};
arr.map(function(value) { 
    var kv = value.split("="); 
    expectedResult[kv[0]] = kv[1]; 
    return value    
})

4 Comments

I found out that reduce works pretty nicely since you can pass in a seed accumulator value as the second parameter (though then you have to return the accumulator at the end of the function).
Also, please note that a querystring such as ?a will break with your code.
As will a querystring such as ?a=b=c, where the key of a has a value of b=c.
@ErikE ah yes, you're totally right, the example I provided doesn't account for any of those scenarios.
0

Try The Below Code.

let arr = ['a=1', 'b=2', 'c=3'];
let b=arr.toString();
b='{"'+(b.split('=').join('":"').split(',').join('","'))+'"}';
b=$.parseJSON(b);
console.log(b);

You will get the required output.

1 Comment

Converting to Json is not a good idea, because the inputs could have quote marks or curly braces in them. Splitting on '=' can lose values such as a=b=c.

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.