2

Given an object:

const obj = { key1: "value1", key2: "value2", key3: "value3", key4: "value4" };

I would like to filter its keys to produce a smaller object. Understand I can do this:

const { key1, key2, ...rest } = obj;

And the rest variable will then be set to { key3: "value3", key4: "value4" }.

But can a similar thing be done dynamically, without key1 and key2 being hard-coded? Let's say they are in an array unwantedKeys, whose value (["key1", "key2"]) and length can only be determined at runtime.

Steve Chambers
  • 37,270
  • 24
  • 156
  • 208
  • 1
    If you do it "dynamically", does that mean it'll magically create `key1` and `key2` variables in scope? – kelsny Feb 24 '23 at 17:32
  • 1
    You *can* destructure dynamically, but you'll need to know the length of the array and assign aliases for each `const { [keys[0]]: k1, [keys[1]]: k2, ...rest } = obj;` but otherwise you'll need to reduce or otherwise iterate over the array and filter the object by the elements in the array. – pilchard Feb 24 '23 at 17:36
  • @vera In the non-dynamic example `key1` and `key2` are unwanted and wouldn't be used afterwards - it's the `rest` variable that is needed. If the dynamic way worked the same that would be fine. – Steve Chambers Feb 24 '23 at 17:36
  • Then you should rewrite your question, just filter the entries by the keys array `const rest = Object.fromEntries(Object.entries(obj).filter(([k]) => !keys.includes(k)));` – pilchard Feb 24 '23 at 17:36
  • 1
    Then are you really just asking for something like lodash omit? – kelsny Feb 24 '23 at 17:37
  • @pilchard True but unfortunately the length of the array would only be known at runtime - have now edited the question to say this. – Steve Chambers Feb 24 '23 at 17:37

1 Answers1

1

But can a similar thing be done dynamically without key1 and key2 being hard-coded? Let's say they are in an array unwantedKeys, whose value (["key1", "key2"]) can only be determined at runtime.

Yes, but not entirely dynamically. You'd need to know how many there were. For instance, for two of them:

const { [unwantedKeys[0]]: unused1, [unwantedKeys[1]]: unused2, ...rest } = obj;

const unwantedKeys = ["key1", "key2"];
const obj = { key1: "value1", key2: "value2", key3: "value3", key4: "value4" };
const { [unwantedKeys[0]]: unused1, [unwantedKeys[1]]: unused2, ...rest } = obj;
console.log(rest);

...which probably means it's not useful for your situation. :-) (Edit: You've now confirmed that indeed, the length is only known at runtime.)

Since you need it to be dynamic, you can't do this with syntax alone; but you can with various standard library tools, like Object.entries, Array.prototype.filter, and Object.fromEntries:

const rest = Object.fromEntries(
    Object.entries(obj).filter(([key]) => !unwanted.includes(key))
);

const unwantedKeys = ["key1", "key2"];
const obj = { key1: "value1", key2: "value2", key3: "value3", key4: "value4" };
const rest = Object.fromEntries(
    Object.entries(obj).filter(([key]) => !unwantedKeys.includes(key))
);
console.log(rest);

Or perhaps with a Set, if unwantedKeys is so massively long that rescanning it is a performance concern (not likely :-) ):

const unwantedSet = new Set(unwantedKeys);
const rest = Object.fromEntries(
    Object.entries(obj).filter(([key]) => !unwantedSet.has(key))
);

const unwantedKeys = ["key1", "key2"];
const obj = { key1: "value1", key2: "value2", key3: "value3", key4: "value4" };
const unwantedSet = new Set(unwantedKeys);
const rest = Object.fromEntries(
    Object.entries(obj).filter(([key]) => !unwantedSet.has(key))
);
console.log(rest);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Perfect answer - the middle solution is nice and compact and working great! :-) The only thing I needed to add was `JSON.parse(JSON.stringify(obj))` to deep-clone the object as since realised the `rest` object needs to be modifiable afterwards without affecting nested objects in `obj`. (Just a note to self - this requirement wasn't mentioned in the question.) – Steve Chambers Feb 26 '23 at 09:58
  • 1
    @SteveChambers - Glad this helped! :-) FWIW, I strongly recommend not doing `JSON.parse(JSON.stringify(obj))`. It's a lossy operation (drops functions, `undefined`, Symbols; `Map`s and `Sets` are converted to `{}`) and it makes an unnecessary round-trip through text. Instead, I'd write a function to do the cloning according to how you want it to work, probably be adapting one of the answers to [this question](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript). Happy coding! – T.J. Crowder Feb 26 '23 at 10:17
  • 1
    Good advice - have actually encountered a couple of those issues soon after posting the comment. `structuredClone` looks like the way to go - just have to upgrade my Node.js version... – Steve Chambers Feb 26 '23 at 11:13