Is it possible to make an object with keys matching another object, values matching function input arguments?

210 views Asked by At

codes in imagination & question here:

function makeCreator(obj) {
  return (...args) => {
    return {
      type: obj.type,
      ...obj.keys: ...args, 

      // the above is invalid js code, but I'd like 
      // the keys and values to be automatically paired
      ///////////////////////////////////////////////////////
      // Question: how to make it work using spread operator only?
      // I mean, is it possible to do everything with "just "the spread operator?
      ///////////////////////////////////////////////////////
    };
  }
}

example input

const obj1 = {
  type: 'something',
  key1: 10,
  key2: 20
};

example output

const generatedForObj = makeCreator(obj1);

// it should equivalents to
function generatedForObj(v1, v2) {
  return {
    type: 'something',
    key1: v1,
    key2: v2,
  };
}

// use it like this
const result = generatedForObj (100, 200); 
// result is { type: 'something', key1: 100, key2: 200 }

Actually I'm trying to implement some kind of action creator of redux, if this information helps.

Also providing a (should-be) working version, but I want to try the spread operator:

function makeCreator(obj) {
  return (...args) => {
    let { type: obj.type, ...exceptType } = obj;

    Object.keys(exceptType).forEach((key, index) => { 
      exceptType[key] = args[index];
    });

    return {
      type: obj.type,
      ...exceptType,
    };
  }
}
1

There are 1 answers

3
T.J. Crowder On BEST ANSWER

It's possible, as of ES2015, but it's probably not a good idea. You can get the string-named properties of the object in a stable order using Object.getOwnPropertyNames. The order is slightly complicated, but in your example, the order will be type, key1, and key2 because

  1. All of those are string-keyed
  2. None of them matches the definition of an array index (no, it doesn't matter that the object isn't an array)
  3. None of them is inherited
  4. That's the order in which the properties were created

So again, it's possible:

const makeCreator = obj => {
  const keys = Object.getOwnPropertyNames(obj).filter(key => key !== "type");
  return (...args) => {
    const result = {type: obj.type};
    keys.forEach((key, index) => {
      result[key] = args[index];
    });
    return result;
  }

const obj1 = {
  type: 'something',
  key1: 10,
  key2: 20
};

const makeCreator = obj => {
  const keys = Object.getOwnPropertyNames(obj).filter(key => key !== "type");
  return (...args) => {
    const result = {type: obj.type};
    keys.forEach((key, index) => {
      result[key] = args[index];
    });
    return result;
  }
};

const generatedForObj = makeCreator(obj1);

// use it like this
const result = generatedForObj (100, 200); 
console.log(result);

But, property order is new as of ES2015+ and that means older JavaScript engines don't support it (necessarily), and it cannot be polyfilled. Better to provide your makeCreator an array of the property names explicitly:

const makeCreator = (obj, keys) => {
  return (...args) => {
    const result = {type: obj.type};
    keys.forEach((key, index) => {
      result[key] = args[index];
    });
    return result;
  }
};

Usage:

const generatedForObj = makeCreator(obj1, ["key1", "key2"]);

const obj1 = {
  type: 'something',
  key1: 10,
  key2: 20
};

const makeCreator = (obj, keys) => {
  return (...args) => {
    const result = {type: obj.type};
    keys.forEach((key, index) => {
      result[key] = args[index];
    });
    return result;
  }
};

const generatedForObj = makeCreator(obj1, ["key1", "key2"]);

// use it like this
const result = generatedForObj (100, 200); 
console.log(result);


Re your comment:

I'd like to know if it's possible to make this work "only using" spread operator? Like what I demoed in the imagination part?

No, spread notation (it's not an operator) can only spread array entries for now; in ES2018 (or now via transpiler) it'll be able to spread object properties and their values (as that proposal is at Stage 3 and likely to go forward in time to be in the 2018 spec), but the names and values come from the same object. It can't draw from two sources (names from one place, values from another).