How can I Display a JavaScript ES6 Map Object to Console? - javascript

I'm using repl.it/languages/javascript.
Do I have to convert it to an object before I print it out?
I've tried
const mapObject = new Map();
mapObject.set(1, 'hello');
console.log(JSON.stringify(mapObject));
console.log(mapObject);
The results are always empty object.
When I use
console.log([...mapObject]);
It prints out an array format.

There is a more simpler solution you can try.
const mapObject = new Map();
mapObject.set(1, 'hello');
console.log([...mapObject.entries()]);
// [[1, "hello"]]
console.log([...mapObject.keys()]);
// [1]
console.log([...mapObject.values()]);
// ["hello"]

Note: This answer is only relevant to the repl.it sandbox environment OP is using
Since you said in the comments that you're using repl.it, there's a trick you can use to write your own "logging strategy".
Note that you shouldn't use this trick in production, mainly because it edits a native prototype. In some Node environment, in your own code, it could be useful though.
The idea is to create an inspect method for Map that iterates over the entries:
Map.prototype.inspect = function() {
return `Map(${mapEntriesToString(this.entries())})`
}
function mapEntriesToString(entries) {
return Array
.from(entries, ([k, v]) => `\n ${k}: ${v}`)
.join("") + "\n";
}
You can see that repl.it supports it here
console.log(new Map([["a", 1], ["b", 2]]));
// Logs:
/*
Map(
a: 1
b: 2
)
*/

I realize this is most likely intended for the browser console but this also occurs in Node. So, you may use this in Node to view Maps (or an Object that may contain Maps):
import * as util from "util";
const map: Map<string, string> = new Map();
map.set("test", "test");
const inspected: string = util.inspect(map);
console.log(inspected);

For simple maps that are of depth 1, just use Object.fromEntries([...map]) to convert your object entries array back to a console loggable object:
const simpleObj = {
a: [1, 2, 3],
b: {c: {d: 42}}
};
const map = new Map(Object.entries(simpleObj));
console.log(Object.fromEntries([...map]));
This fails for complex nested maps, however. For that, we can recursively convert any Maps to objects and then log it as normal. Here's a proof-of-concept on a complex structure combining plain objects, Maps, arrays and primitives. Don't expect it to cover every edge case out of the box, but feel free to point out improvements.
const convertMapToObjDeeply = o => {
const recurseOnEntries = a => Object.fromEntries(
a.map(([k, v]) => [k, convertMapToObjDeeply(v)])
);
if (o instanceof Map) {
return recurseOnEntries([...o]);
}
else if (Array.isArray(o)) {
return o.map(convertMapToObjDeeply);
}
else if (typeof o === "object" && o !== null) {
return recurseOnEntries(Object.entries(o));
}
return o;
};
const mkMap = o => new Map(Object.entries(o));
const map = mkMap({
a: 42,
b: [1, 2, 3, mkMap({d: mkMap({ff: 55})})],
c: mkMap({
e: [4, 5, 6, {f: 5, x: y => y}, mkMap({g: z => 1})]
}),
h: {i: mkMap({j: 46, jj: new Map([[[1, 6], 2]])})},
k: mkMap({l: mkMap({m: [2, 5, x => x, 99]})})
});
console.log(convertMapToObjDeeply(map));

you can use console.log(mapObject.values())

Try this:
let mapObject = new Map();
mapObject.set("one", "file1");
mapObject.set("two", "file2");
mapObject.set("three", "file3");
console.log([...mapObject.entries()]);
the output will be:
[ [ 'one', 'file1' ], [ 'two', 'file2' ], [ 'three', 'file3' ] ]
The problem with this solution, is that the output changes if we add a previous string:
console.log("This is a map: " + [...mapObject.entries()]);
the output will be:
This is a map: one,file1,two,file2,three,file3
the solution can be found here

Related

Destructure from dynamic key

Suppose I have some key-value object. I want to destructure dynamically from some key such that I can remove it and just get the remaining items in a new object.
const omit = (obj, key) => {
const { [key], ...rest } = obj // Syntax error
return rest
}
omit({ b: 1, c: 2, d: 3 }, 'd')
// desired output { b: 1, c: 2 }
Is there a way to do that?
Disclaimer: I know there are lots of workarounds but would like to do it with destructuring.
In order to destructure on a dynamic key you will need to provide an alias for JS to bind that value to.
Firefox even gives you a helpful error message here:
const omit = (obj, key) => {
const { [key]: _, ...rest } = obj
// CHANGE -----^
return rest
}
console.log(omit({ b: 1, c: 2, d: 3 }, 'd'))
You can rename the variables when destructuring, and the left side (preexisting name) can be in brackets like you want.
let {[key]: omitted, ...rest} = a;

Remove random parts of an object (Chaos Monkey Style)

I have a JavaScript object e.g.:
const testFixture = {
a: [
{b:1},
{b:2},
{b:3},
],
b: {c: {d: 44, e: "foo", f: [1,2,3]}}
c: 3,
d: false,
f: "Blah",
}
I'd like to have a function I could pass this object to that would mutate it to remove random properties from it, so that I can test whether the thing that uses this object displays an error state, rather than silently erroring.
Edit:
To be clear, I mean any deeply nested property. e.g. it might remove a.b.c.d.e.f[1] or a[2].b
Edit 2:
Here's a buggy solution I'm working on based on ideas from Eureka and mkaatman's answers.
It seems to be changing key names to "undefined" which I wasn't expecting. It's also changing numbers to {} which I wasn't expecting. Not sure why.
var testFixture2 = {
a: [{
b: 1, c: 2
},
{
b: 2, c: 2
},
{
b: 3, c: 2, d: "bar"
},
],
b: {
c: {
d: 44,
e: "foo",
f: [1, 2, 3]
}
},
c: 3,
d: false,
f: "Blah"
};
function getRandomIndex(max) {
return Math.floor(Math.random() * max);
}
function chaosMonkey(thing) {
if (typeof thing === "object") {
console.log("object", Object.keys(thing).length, thing);
const newlyDeformedObject = { ...thing};
// Make a list of all the keys
const keys = Object.keys(thing);
// Choose one at random
const iKey = getRandomIndex(keys.length);
let target = newlyDeformedObject[keys[iKey]];
const shouldDelete = getRandomIndex(3) === 0;
if (shouldDelete) {
delete target;
console.log("Object deleted", keys[iKey]);
} else {
console.log("+++ Going deeper", thing);
newlyDeformedObject[keys[iKey]] = chaosMonkey({ ...newlyDeformedObject[keys[iKey]] });
}
return newlyDeformedObject;
} else if (typeof thing === "array") {
console.log(array);
const iKey = getRandomIndex(thing.length);
const shouldDelete = getRandomIndex(3) === 0;
if (shouldDelete) {
delete array[iKey];
console.log("Array deleted", iKey);
} else {
array[iKey] = chaosMonkey(array[iKey]);
return array;
}
} else {
//#todo do something bad based on type e.g. number -> NaN, string -> '', but these are less likely to break something
delete thing;
return;
}
}
console.log(JSON.stringify(chaosMonkey(testFixture2), null, 2));
NB: the chances of any object key or array item being recursed into are equal, in order to make modifications equally likely anywhere in the object.
Edit 3:
Additional Requirement:
It MUST always remove at least one thing.
Bonus points for:
ways to control the number of things that get deleted
any way to limit which properties get deleted or recursed into.
i.e. allow/deny lists, where:
allowRemovalList = properties that it's ok to remove
denyRemovalList = properties that it's not ok to remove
(It could be that you have some properties that it's ok to remove entirely, but they should not be recursed into and inner parts of them removed.)
NB: Originally I asked for whitelist/blacklist but this caused confusion (and I wouldn't want anyone copying this code to be surprised when they use it) and some answers have implemented it so that blacklist = properties to always remove. I won't penalise any answer for that (and it's trivial to change anyway).
I took a stab at it because I thought the question was interesting and unique. This is a bit sloppy but maybe it's a start if someone else is wondering how to do this in the future.
const testFixture = {
a: [{
b: 1
},
{
b: 2
},
{
b: 3
},
],
b: {
c: {
d: 44,
e: "foo",
f: [1, 2, 3]
}
},
c: 3,
d: false,
f: "Blah"
};
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
function chaosMonkey(object) {
console.log(Object.keys(testFixture).length, object);
const newlyDeformedObject = { ...object
};
Object.keys(testFixture).forEach((item, index) => {
const shouldDelete = getRandomInt(2);
console.log(index, shouldDelete);
if (shouldDelete) {
delete newlyDeformedObject[item];
} else {
if (typeof newlyDeformedObject[item] === "object") {
console.log("+++ Going deeper", { ...newlyDeformedObject[item]
});
newlyDeformedObject[item] = chaosMonkey({ ...newlyDeformedObject[item]
});
}
}
});
return newlyDeformedObject;
}
console.log(chaosMonkey(testFixture));
Assuming you mean random properties of the root of the object (not properties of properties or properties of array elements)
const testFixture = {
a: [{
b: 1
},
{
b: 2
},
{
b: 3
},
],
b: {
c: {
d: 44,
e: "foo",
f: [1, 2, 3]
}
},
c: 3,
d: false,
f: "Blah",
}
// Make a list of all the keys
const keys = Object.keys(testFixture);
// Choose one at random
const iKey = Math.floor(Math.random() * keys.length);
// (For simplicity we are making the assumption that there will always be at least one key)
const deleteKey = keys[iKey]
// Build a new object, that has the all the properties of the old one, except the property selected for deletion.
const out = {};
keys.forEach(key => {
if (key !== deleteKey) {
out[key] = testFixture[key]
}
})
console.log(out)
Modifying the OP's code, to achieve deep deletion
Great that you have joined in the coding and shown a nearly-complete answer! That is much more likely to engage people's curiosity. I think your code is nearly there: just make sure to do your deletion directly from the target object, otherwise you only delete a temporary variable "target".
Does the below do what you want? (Only one line changed)
var testFixture2 = {
a: [{
b: 1,
c: 2
},
{
b: 2,
c: 2
},
{
b: 3,
c: 2,
d: "bar"
},
],
b: {
c: {
d: 44,
e: "foo",
f: [1, 2, 3]
}
},
c: 3,
d: false,
f: "Blah"
};
function getRandomIndex(max) {
return Math.floor(Math.random() * max);
}
function chaosMonkey(thing) {
if (typeof thing === "object") {
console.log("object", Object.keys(thing).length, thing);
const newlyDeformedObject = { ...thing
};
// Make a list of all the keys
const keys = Object.keys(thing);
// Choose one at random
const iKey = getRandomIndex(keys.length);
let target = newlyDeformedObject[keys[iKey]];
const shouldDelete = getRandomIndex(3) === 0;
if (shouldDelete) {
// In this line below, we delete the property from "newlyDeformedObject", not just delete the variable "target"
delete newlyDeformedObject[keys[iKey]];
console.log("Object deleted", keys[iKey]);
} else {
console.log("+++ Going deeper", thing);
newlyDeformedObject[keys[iKey]] = chaosMonkey({ ...newlyDeformedObject[keys[iKey]]
});
}
return newlyDeformedObject;
} else if (typeof thing === "array") {
console.log(array);
const iKey = getRandomIndex(thing.length);
const shouldDelete = getRandomIndex(3) === 0;
if (shouldDelete) {
delete array[iKey];
console.log("Array deleted", iKey);
} else {
array[iKey] = chaosMonkey(array[iKey]);
return array;
}
} else {
//#todo do something bad based on type e.g. number -> NaN, string -> '', but these are less likely to break something
delete thing;
return;
}
}
console.log(JSON.stringify(chaosMonkey(testFixture2), null, 2));
Here is a solution that features whitelisting and blacklisting, considering blacklisting takes priority over whitelisting:
const testFixture = {
a: [{ b: 1 }, { b: 2 }, { b: 3 }],
b: { c: { d: 44, e: "foo", f: [1, 2, 3] } },
c: 3,
d: false,
f: "Blah",
};
const whiteList = [
["a", "2", "b"],
["b", "c", "e"],
];
const blackList = [
["a", "1", "b"],
["b", "c", "d"],
];
// Partial match because if a sub-property is whitelisted, the full path has to remain untouched
const isInWhiteList = (input) =>
whiteList.some((x) =>
input.reduce((acc, cur, i) => cur === x[i] && acc, true)
);
// Exact match
const isInBlackList = (input) =>
blackList.some(
(x) =>
x.length === input.length &&
input.reduce((acc, cur, i) => cur === x[i] && acc, true)
);
const chaosMonkey = (
input,
chanceOfBeingDeleted = 0.2, // Probability of property deletion
deep = true, // Remove only the deepest properties? If set to false, removes intermediate ones as well
path = []
) => {
if (typeof input !== "object") return;
const propsToDelete = [];
const itemsDeletedArr = [];
// Calculate properties to delete
for (const item in input) {
const currentPath = [...path, item];
if (
(isInBlackList(currentPath) ||
(!isInWhiteList(currentPath) &&
Math.random() < chanceOfBeingDeleted)) &&
(!deep || typeof input[item] !== "object")
) {
propsToDelete.push(item);
} else {
const itemsDeleted = chaosMonkey(
input[item],
chanceOfBeingDeleted,
deep,
currentPath
);
itemsDeletedArr.push(itemsDeleted);
}
}
// Delete properties
if (input instanceof Array) {
// Delete indexes in reverse direction to prevent indexes shifting
propsToDelete.reverse().forEach((x) => input.splice(x, 1));
} else {
propsToDelete.forEach((x) => delete input[x]);
}
// Has deleted at least one property?
return (
!!propsToDelete.length ||
itemsDeletedArr.reduce((acc, cur) => acc || cur, false)
);
};
// Optionally pass a chance of being deleted as second parameter
while (!chaosMonkey(testFixture)) {
console.log("chaosMonkey didn't change anything, retrying");
}
console.log(testFixture);
I have a very different solution that depending on your needs might be a clever one or not very useful.
Javascript can define a Proxy to be the interface between an object and whatever script is using that object. For example:
const obj = { a: 2 }
const proxy = new Proxy(obj, {
get: () => 3
})
console.log(obj.a) // 2
console.log(proxy.a) // 3
So I'm proposing a russian doll of proxies so that every time something tries to access any property of your object, deeply nested or not, there is some likelihood to receive undefined instead.
const obj = {
a: [
{ b: 1 },
{ b: 2 },
{ b: 3 },
],
b: {
c: {
d: 44,
e: "foo",
f: [1, 2, 3],
},
},
c: 3,
d: false,
f: "Blah",
}
function makeRandomlyInaccessible(object, likelihood) {
const handler = {
get(target, key, receiver) {
if (
key in target
&& Math.random() < likelihood
) {
return undefined
}
const result = Reflect.get(target, key, receiver)
if (typeof result === "object") {
return new Proxy(result, handler)
}
return result
},
}
return new Proxy(object, handler)
}
const monkeyObj = makeRandomlyInaccessible(obj, .1)
console.log(monkeyObj.a[1].b) // 2
console.log(monkeyObj.a[1].b) // 2
console.log(monkeyObj.a[1].b) // undefined
console.log(monkeyObj.a[1].b) // 2
console.log(monkeyObj.a[1].b) // Uncaught TypeError: Cannot read property '1' of undefined
The property get I'm defining in this proxy is called a "trap". This one traps every call to get a property value. But there are many other traps you could play with depending on what you need / want to try.
If you need the object to be stable (meaning that random properties are missing, but if they are found they are always found, and if they are undefined they are always undefined), you could just memoize the results of the traps like this:
function makeRandomlyInaccessible(object, likelihood) {
const trapMap = new Map()
const handler = {
get(target, key) {
if (!trapMap.has(target)) {
trapMap.set(target, {})
}
const traps = trapMap.get(target)
if (key in traps) {
return traps[key]
}
if (
!(key in traps)
&& Math.random() < likelihood
) {
traps[key] = undefined
return undefined
}
const result = target[key]
if (typeof result === "object" && result !== null) {
traps[key] = new Proxy(result, handler)
} else {
traps[key] = result
}
return traps[key]
},
}
return new Proxy(object, handler)
}
This approach allows you to define lists to customize the behavior, where each property is defined as a "path to the property" (for example "b.c.f[1]"), as well as the exact deleteCount.
doNotRemove: properties specified here must not be removed, which implies that all of their parents must not be removed either
mustRemove: properties specified here must be removed
canRemoveButNotModify: properties specified here can be removed, but all of their children must not be removed
This approach could easily be tweaked to use a "delete ratio" instead of an absolute count.
The basic principle is:
we recursively go over the entire object (breadth first) and construct a list of "path to a property", with a JS-like grammar (for example "b.c.f[1]")
we match the obtained list with doNotRemove, mustRemove, and canRemoveButNotModify (thanks to a few very basic utility functions) to get a list of properties to delete
we delete the properties in the list
const obj = {
a: [
{ b: 1 },
{ b: 2 },
{ b: 3 },
],
b: {
c: {
d: 44,
e: "foo",
f: [1, 2, 3],
},
},
c: 3,
d: false,
f: "Blah",
}
deleteRandomProps(obj, {
deleteCount: 5,
doNotRemove: [
'a[1]',
'b.c',
],
mustRemove: [
'a[2]',
'b.c.e',
],
canRemoveButNotModify: [
"b.c.f"
],
})
console.log('result', obj)
function deleteRandomProps(obj, {
deleteCount = 0, // number of deletion
doNotRemove = [], // prevent deletion of property and all its children, of the shape "a[0].b"
mustRemove = [], // force deletion of property, of the shape "a[0].b"
canRemoveButNotModify = [], // can delete property entirely but not modify its children, of the shape "a[0].b"
}) {
// list all possible property paths
const listOfAllPaths = listPaths(obj)
// prevent deletion of doNotRemove items
doNotRemove
.flatMap(path => allPathsThatLeadToPath(path))
.forEach(path => {
// remove all of doNotRemove from full list
removeItemFromList(path, listOfAllPaths)
// remove all of doNotRemove from mustRemove
removeItemFromList(path, mustRemove)
})
// prevent deletion of items that are children of canRemoveButNotModify items
canRemoveButNotModify
.forEach(path => {
// remove from full list
removeChildPaths(path, listOfAllPaths)
// remove from mustRemove
removeChildPaths(path, mustRemove)
})
// remove from list all properties that are children of a property in mustRemove
mustRemove.forEach(path => {
removeItemFromList(path, listOfAllPaths)
removeChildPaths(path, listOfAllPaths)
})
// start from mustRemove and add until deleteCount is reached
const deletions = [...mustRemove]
while (deletions.length < deleteCount && listOfAllPaths.length > 0) {
const path = removeRandomItemFromList(listOfAllPaths)
// remove from list all properties that are children of the one we're deleting
removeItemFromList(path, listOfAllPaths)
removeChildPaths(path, listOfAllPaths)
// remove from deletions all properties that are children of the new one
removeItemFromList(path, deletions)
removeChildPaths(path, deletions)
deletions.push(path)
}
// delete properties from object
console.log('deleting props', deletions)
deletions.forEach(path => {
deleteFromPath(obj, path)
})
return obj
}
// create a list of all possible property paths, of the shape "a[0].b"
function listPaths(obj, prefix = '') {
if (typeof obj !== 'object') {
return []
}
const pureKeys = Object.keys(obj)
const keys = prefix
? pureKeys.map(key => `${prefix}.${key}`)
: pureKeys
const values = Object.values(obj)
const more = []
values.forEach((value, index) => {
if (Array.isArray(value)) {
value.forEach((item, i) => {
const newKey = `${keys[index]}[${i}]`
more.push(newKey)
more.push(...listPaths(item, newKey))
})
} else if (typeof value === 'object') {
more.push(...listPaths(value, keys[index]))
}
})
return [...keys, ...more]
}
// recursively find property based on list of keys, of the shape ["a", "0", "b"]
function findFromArray(obj, array) {
if (array.length === 0) {
return obj
}
const [key, ...rest] = array
if (key in obj) {
return findFromArray(obj[key], rest)
}
}
// turn path into list of keys ("a[0].b" => ["a", "0", "b"])
function pathToParticles(path) {
const regex = /([a-z0-9])/gi
const particles = path.match(regex)
return particles || []
}
// deletes a property based on property path, of the shape "a[0].b"
function deleteFromPath(obj, path) {
const particles = pathToParticles(path)
const last = particles.pop()
const lastObject = findFromArray(obj, particles)
if (lastObject) {
delete lastObject[last]
}
}
// turn path into list of path that lead to it ("a[0].b" => ["a", "a[0]", "a[0].b"])
function allPathsThatLeadToPath(path) {
const regex = /[\[\.]/gi
const list = [path]
let match
while ((match = regex.exec(path)) !== null) {
list.push(path.substring(0, match.index))
}
return list
}
// remove item from an array
function removeItemFromList(item, list) {
const index = list.indexOf(item)
if (index !== -1) {
list.splice(index, 1)
}
return list
}
// remove from list all property paths that are children of a property path
// for example "a[0]" will remove "a[0].b", "a[0].b.c", ... but not "a[0]" itself
function removeChildPaths(path, list) {
for(let i = list.length; i > 0; i--) {
const item = list[i-1]
if (item !== path && item.startsWith(path)) {
list.splice(i-1, 1)
}
}
}
// remove a random prop from a list
function removeRandomItemFromList(list) {
const index = Math.floor(Math.random() * list.length)
const [item] = list.splice(index, 1)
return item
}
I went for a more simplistic approach. My function builds out a list of all key-value pairs and selects one at random each time it runs. For the sake of this example, I log the name of the removed key each time the function runs along with the stringified object, then run it again, until all keys are successfully removed.
Solution #1
const testFixture = {
a: [{b:1},{b:2},{b:3},],
b: {c: {d: 44, e: "foo", f: [1,2,3]}},
c: 3,
d: false,
f: "Blah",
};
const chaosMonkey = obj => {
const getObjKeysPairs = (obj, parent = false) => {
const objKeysPairs = [];
for (const [key, value] of Object.entries(obj)) {
const parentKeyPair = parent ? parent + "." + key : key;
objKeysPairs.push([obj, key, parentKeyPair]);
if (typeof value === 'object' && value !== null) {
objKeysPairs.push(...getObjKeysPairs(value, parentKeyPair));
}
}
return objKeysPairs;
};
const objKeyPairs = getObjKeysPairs(obj),
[object, key, path] = objKeyPairs[Math.floor(Math.random() * objKeyPairs.length)];
console.log("Deleting " + path + " === " + object[key]);
delete object[key];
};
while (Object.keys(testFixture).length) {
chaosMonkey(testFixture);
}
console.log("testFixture:", testFixture);
if (Object.keys(testFixture).length === 0) console.log("testFixture emptied successfully");
This solution works as-is and deletes keys recursively for both arrays and objects. Using the delete operator however, array items are deleted but not entirely removed, leaving an empty value remaining.
This does not throw any errors, even silently, because empty values are not recognized as valid key-value pairs and therefore are not "re-attempted" as values once deleted initially.
NOTE: The below explanation is for the first version of this solution. It is still valuable and so I am not removing it, but please see the older version of this solution to see its relevance.
One JS concept worthy of a deeper explanation is this:
if ([Object, Array].includes(value.constructor)) { /* ... */ }
Every data type in JavaScript is built upon classes. Up until recently, classes were defined using a functional syntax. ES6+ introduced a new class declaration, which makes creating new classes arguably much easier and essentially decorates the classical syntax.
Each class's constructor can be referenced by its name like a variable (without quotes) or by its name as a string. Here is an example of this:
class CustomObject extends Object {
constructor() {
super();
}
}
const myVar = new CustomObject();
myVar.constructor === CustomObject; // true
myVar.constructor.name === "CustomObject"; // true
Another important concept here is the prototypal chain. Every object and type in JavaScript is a prototyped instance of another object, so if you traverse through the prototypal chain you can evaluate any ancestor prototype of any given object, even simple strings or numbers, until you reach the most primitive object which has no prototype and is the ultimate prototype inherited by all types in JavaScript. Here are a few examples of that:
(5).constructor === (5).__proto__?.constructor; // true
(5).__proto__.constructor; // => Number
(5).__proto__.__proto__?.constructor; // => Object
(5).__proto__.__proto__.__proto__?.constructor; // => undefined
"string".constructor === "string".__proto__?.constructor; // true
"string".__proto__.constructor; // => String
"string".__proto__.__proto__?.constructor; // => Object
"string".__proto__.__proto__.__proto__?.constructor; // => undefined
class CustomObject extends Object { constructor() { super(); }}
const myVar = new CustomObject();
myVar.__proto__.constructor; // => CustomObject
myVar.__proto__.__proto__?.constructor; // => Object
myVar.__proto__.__proto__.__proto__?.constructor; // => undefined
In your example, this is all very useful for us for the simple purpose of checking whether the value of one of the object's values, regardless of depth, is a nested object or array. Like the examples above, arrays and objects also have unique constructors. The constructor for arrays is Array and the constructor for objects is Object. I am simply checking to see if the value being evaluated is either one of those types and if so evaluate its own child properties/elements via recursion.
Recursion, if you aren't already familiar with it is the process by which a function calls itself, often passing information allowing the function to reach whatever depth necessary to gather all the information available. This is exactly what we need in your case to build the complete list of all properties in the main object.
Lastly, my purpose in using [Object, Array].includes(...) is simply because is a bit easier than using value.constructor === Object || value.constructor === Array. Using the includes Array.prototype method allows us in this case to check whether the value we are checking, value.constructor is equal to either Object or Array.
There is a spec floating around out there for use of the bitwise OR operator | to do something like this so we can write these more naturally like this: value.constructor === Object | Array. However, this is purely conceptual at this point and may only work in specific use-cases such as XHTTP requests and fetch.
Solution #2
In my second solution below, I add an additional check at the end of the function to test whether object.constructor === Array and if so, I use the splice Array.prototype method to completely remove the array element rather than only deleting it, which leaves an empty value behind at the deleted index.
If however, you would prefer to remove the array items altogether as the function runs, you can do so using the splice array method and simply run the splice conditionally based on the constructor.
Here is how that would work:
const testFixture = {
a: [{b:1},{b:2},{b:3},],
b: {c: {d: 44, e: "foo", f: [1,2,3]}},
c: 3,
d: false,
f: "Blah",
};
const chaosMonkey = obj => {
const getObjKeysPairs = (obj, parent = false) => {
const objKeysPairs = [];
for (const [key, value] of Object.entries(obj)) {
const parentKeyPair = parent ? parent + "." + key : key;
objKeysPairs.push([obj, key, parentKeyPair]);
if (typeof value === 'object' && value !== null) {
objKeysPairs.push(...getObjKeysPairs(value, parentKeyPair));
}
}
return objKeysPairs;
};
const objKeyPairs = getObjKeysPairs(obj),
[object, key, path] = objKeyPairs[Math.floor(Math.random() * objKeyPairs.length)];
console.log("Deleting " + path + " === " + object[key]);
delete object[key];
if (Array.isArray(object)) object.splice(key, 1);
};
while (Object.keys(testFixture).length) {
chaosMonkey(testFixture);
}
console.log("testFixture:", testFixture);
if (Object.keys(testFixture).length === 0) console.log("testFixture emptied successfully");
Here is an answer I'm not entirely happy with. It does randomly remove leaf properties from an object, and it does that fairly well.
But -- and while the question did not specify this, I think it's an interesting possible extension -- this version only removes leaf nodes; it might be preferable to also remove other branches. While I did think through some possibilities here, nothing really seemed to gel. It's not a matter of implementation, but of coming up with good requirements. Do we treat all paths equally? What does it mean to remove a branch when we want to keep some of its subbranches? Should we weight the chances of removal higher for leaves than for heavy-weight branches? And so on.
But, for what this does do, I'm pretty happy with this solution. It basically converts an object to an array of pairs representing paths to a node and that node's value. Then we randomly delete some entries from that array and reconstitute an object from the remaining values.
It looks like this:
// utility functions
const pathEntries = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, x]) => pathEntries (x) .map (([p, v]) => [[Array.isArray(obj) ? Number(k) : k, ... p], v])
)
: [[[], obj]]
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number.isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const hydrate = (xs) =>
xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})
// helper function
const randomRemove = (xs, count = 1, i = Math .floor (Math .random () * xs .length)) =>
count <= 0
? [...xs]
: randomRemove ([... xs .slice (0, i), ... xs .slice (i + 1)], count - 1)
// main function
const chaosMonkey = (o, count = 1) =>
hydrate (
randomRemove (pathEntries (o), count)
)
// sample data
const testFixture = {a: [{b: 1}, {b: 2}, {b: 3}], b: {c: {d: 44, e: "foo", f: [1, 2, 3]}}, c: 3, d: false, f: "Blah"}
// demo
console .log (chaosMonkey (testFixture, 3))
.as-console-wrapper {max-height: 100% !important; top: 0}
This is built on three utility functions that I've used often on StackOverflow:
pathEntries creates an array of items such as [['a', 2, 'b'], 3], which describes the property b of the element with index 2 in the array stored in the a property of the root object, and notes that it has value 3.
setPath takes a path and value like that and a base object and sets the corresponding value along that path, creating new nodes as needed.
hydrate simply runs setPath for each of an array of such entries, building a new minimal object with those values.
We also create a simple helper function randomRemove which removes random elements from an array. This is a simple recursion on the number of items to remove, returning the original array if the count is zero, otherwise removing one random element and recurring with one less than count.
On top of those, we write our chaosMonkey function, that simply calls pathEntries on the object, calls randomRemove on the result and then hydrate to build a new object.
Besides the large question mentioned above, there are two possible changes I imagine we might consider. Most importantly, this will allow us to remove entries in the middle of our arrays, leaving us with sparse arrays. We may want to avoid that. This is a naive fix for that problem:
const chaosMonkey = (o, count = 1) =>
hydrate (
randomRemove (pathEntries (o) .filter (xs => !Number .isInteger (xs [xs .length])), count)
)
which avoids removing any array elements at all. You can see it in this snippet:
// utility functions
const pathEntries = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, x]) => pathEntries (x) .map (([p, v]) => [[Array.isArray(obj) ? Number(k) : k, ... p], v])
)
: [[[], obj]]
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number.isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const hydrate = (xs) =>
xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})
// helper function
const randomRemove = (xs, count = 1, i = Math .floor (Math .random () * xs .length)) =>
count <= 0 ? [...xs] : randomRemove ([... xs .slice (0, i), ... xs .slice (i + 1)], count - 1)
// main function
const chaosMonkey = (o, count = 1) =>
hydrate (
randomRemove (pathEntries (o) .filter (xs => !Number .isInteger (xs [xs .length])), count)
)
// sample data
const testFixture = {a: [{b: 1}, {b: 2}, {b: 3}], b: {c: {d: 44, e: "foo", f: [1, 2, 3]}}, c: 3, d: false, f: "Blah"}
// demo
console .log (chaosMonkey (testFixture, 3))
.as-console-wrapper {max-height: 100% !important; top: 0}
We might consider a more sophisticated version of this that actually does delete array entries but doesn't leave the holes. While we could extend this technique to handle that, it might be more effort than it's worth, and perhaps we would instead write a more sophisticated hydrate.
Second, this requires us to specify the number of paths to delete. It seems more in the spirit of the problem to make that number also random, or to consider a fraction of the paths involved, or do something still more interesting. I think these would mostly be simple variants of the above, and I leave that to the reader.

What is a good way destructure two objects into one, joining specific keys?

Given two objects with different but known keys - which may or may not be set - what is a good way to combine them into one object using specific keymaps for both?
Say, for example, obj1 can have keys itemA, itemB, and itemC. Another obj2 can have keys item_a, item_b, and item_c, and takes precedence. The keys refer to the same final keys a, b, and c respectively.
This is what I came up with, but it feels unnecessarily convoluted.
// Given these objects:
const obj1 = { itemA: 1, itemB: 2, itemC: undefined }
const obj2 = { item_a: undefined, item_b: 20, item_c: 30 }
// Desired result: { a: 1, b: 20, c: 30 }
const filter = obj => Object.fromEntries(
Object.entries(obj).filter(entry => entry[1])
)
const serialized1 = (object =>
({ itemA:a, itemB:b, itemC:c } = object)
&& filter({a, b, c}))(obj1)
const serialized2 = (object =>
({ item_a:a, item_b:b, item_c:c } = object)
&& filter({a, b, c}))(obj2)
const finalObject = { ...serialized1, ...serialized2 }
console.log(finalObject)
// { a: 1, b: 20, c: 30 }
I'm roughly trying to have a keymap for each object for which foreign keys map to my local keys, which then merge.
Note: The mentioned key names are examples. In reality it can be arbitrary, like one of im-dimensions and img-res being stored in a key called size.
Using Stringify and Parse,we can achieve that
const obj1 = { itemA: 1, itemB: 2 };
const obj2 = { item_b: 20, item_c: 30 };
let format=(obj)=>{
return JSON.parse(JSON.stringify(obj).toLowerCase().replace(/\"(\w*)([a-c]{1}\":)/g,"\"$2"));
}
let obj3={...format(obj1),...format(obj2)}
console.log(obj3);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I found a solution. You can create a map of oddly named keys to their preferred names, and "rename" them into a new object using the ES6 Initializer Specification. Basically that means you can use a variable as a key name like so:
const obj = {
[key]: value
}
So with our map, we can create a new key with the value from our old object:
const obj = {
[map[key]]: obj[key]
}
In order to automate this for every key in an object, we can use the reduce function in conjunction with the spread syntax:
const mapper = (map, obj) =>
Object.keys(obj)
.filter(key => Boolean(obj[key]))
.reduce((acc, key) =>
({
...acc,
...{ [map[key]]: obj[key] }
}), {})
This is a very versatile and reusable function. All we need is to supply the map and the input object. Here's a full example based on the data from the original question:
// Given these objects:
const obj1 = { itemA: 1, itemB: 2, itemC: undefined }
const obj2 = { item_a: undefined, item_b: 20, item_c: 30 }
// Desired result: { a: 1, b: 20, c: 30 }
// ------
// Map keys to predefined attributes
const map = {
itemA: 'a',
item_a: 'a',
itemB: 'b',
item_b: 'b',
itemC: 'c',
item_c: 'c'
}
const mapper = (map, obj) =>
Object.keys(obj)
.filter(key => Boolean(obj[key]))
.reduce((acc, key) =>
({
...acc,
...{ [map[key]]: obj[key] }
}), {})
const keymap = mapper.bind(null, map)
const attributes = { ...keymap(obj1), ...keymap(obj2) }
console.log(attributes)
// { a: 1, b: 20, c: 30 }

ES6 returning destructed function argument [duplicate]

Say I have an object:
elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
I want to make a new object with a subset of its properties.
// pseudo code
subset = elmo.slice('color', 'height')
//=> { color: 'red', height: 'unknown' }
How may I achieve this?
Using Object Destructuring and Property Shorthand
const object = { a: 5, b: 6, c: 7 };
const picked = (({ a, c }) => ({ a, c }))(object);
console.log(picked); // { a: 5, c: 7 }
From Philipp Kewisch:
This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form
let unwrap = ({a, c}) => ({a, c});
let unwrap2 = function({a, c}) { return { a, c }; };
let picked = unwrap({ a: 5, b: 6, c: 7 });
let picked2 = unwrap2({a: 5, b: 6, c: 7})
console.log(picked)
console.log(picked2)
Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.
Given:
var obj = { 'foo-bar': 1, bar: 2, qux: 3 };
The expected result for regular picking of foo-bar, bar, baz keys:
{ 'foo-bar': 1, bar: 2 }
The expected result for inclusive picking:
{ 'foo-bar': 1, bar: 2, baz: undefined }
Destructuring
Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.
The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.
The upside is that it's performant solution that is natural to ES6.
The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.
IIFE
const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);
Temporary variables
const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };
A list of strings
Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].
ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.
One-liners
Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.
Pick (ES5)
var subset = ['foo-bar', 'bar', 'baz']
.reduce(function (obj2, key) {
if (key in obj) // line can be removed to make it inclusive
obj2[key] = obj[key];
return obj2;
}, {});
Omit (ES5)
var subset = Object.keys(obj)
.filter(function (key) {
return ['baz', 'qux'].indexOf(key) < 0;
})
.reduce(function (obj2, key) {
obj2[key] = obj[key];
return obj2;
}, {});
Pick (ES6)
const subset = ['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Omit (ES6)
const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Pick (ES2019)
const subset = Object.fromEntries(
['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.map(key => [key, obj[key]])
);
Omit (ES2019)
const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);
Reusable functions
One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').
JavaScript
const pick = (obj, ...keys) => Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
);
const inclusivePick = (obj, ...keys) => Object.fromEntries(
keys.map(key => [key, obj[key]])
);
const omit = (obj, ...keys) => Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key))
);
TypeScript
Credit goes to #Claude.
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
) as Pick<T, K>
);
const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
obj: T, ...keys: K[]
) => (
Object.fromEntries(
keys
.map(key => [key, obj[key as unknown as keyof T]])
) as {[key in K]: key extends keyof T ? T[key] : undefined}
)
const omit = <T extends {}, K extends keyof T>(
obj: T, ...keys: K[]
) =>(
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>
)
I suggest taking a look at Lodash; it has a lot of great utility functions.
For example pick() would be exactly what you seek:
var subset = _.pick(elmo, ['color', 'height']);
fiddle
If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.
const object = {
a: 'a',
b: 'b',
c: 'c',
d: 'd',
}
// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};
console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};
While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.
var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});
This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.
While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.
edit: es7 version of the same:
const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});
edit: A nice example for currying, too! Have a 'pick' function return another function.
const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.
pick('color', 'height')(elmo);
What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:
[elmo, grover, bigBird].map(pick('color', 'height'));
// [
// { color: 'red', height: 'short' },
// { color: 'blue', height: 'medium' },
// { color: 'yellow', height: 'tall' },
// ]
I am adding this answer because none of the answer used Comma operator.
It's very easy with destructuring assignment and , operator
const object = { a: 5, b: 6, c: 7 };
const picked = ({a,c} = object, {a,c})
console.log(picked);
One more solution:
var subset = {
color: elmo.color,
height: elmo.height
}
This looks far more readable to me than pretty much any answer so far, but maybe that's just me!
There is nothing like that built-in to the core library, but you can use object destructuring to do it...
const {color, height} = sourceObject;
const newObject = {color, height};
You could also write a utility function do it...
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
Libraries such as Lodash also have _.pick().
TypeScript solution:
function pick<T extends object, U extends keyof T>(
obj: T,
paths: Array<U>
): Pick<T, U> {
const ret = Object.create(null);
for (const k of paths) {
ret[k] = obj[k];
}
return ret;
}
The typing information even allows for auto-completion:
Credit to DefinitelyTyped for U extends keyof T trick!
TypeScript Playground
I want to mention that very good curation here:
pick-es2019.js
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
);
pick-es2017.js
Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
pick-es2015.js
Object.keys(obj)
.filter((key) => ['whitelisted', 'keys'].indexOf(key) >= 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
omit-es2019.js
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
);
omit-es2017.js
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
omit-es2015.js
Object.keys(obj)
.filter((key) => ['blacklisted', 'keys'].indexOf(key) < 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
You can use Lodash also.
var subset = _.pick(elmo ,'color', 'height');
Complementing, let's say you have an array of "elmo"s :
elmos = [{
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
},{
color: 'blue',
annoying: true,
height: 'known',
meta: { one: '1', two: '2'}
},{
color: 'yellow',
annoying: false,
height: 'unknown',
meta: { one: '1', two: '2'}
}
];
If you want the same behavior, using lodash, you would just:
var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.
To set keys dynamically, you can use reduce function without mutating object as follows:
const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});
const elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)
Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen
below is a version using reduce with single clone (updating initial value passed in to reduce).
const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
acc[curr] = obj[curr]
return acc
}, {})
const elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)
Dynamic solution
['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})
let subset= (obj,keys)=> keys.reduce((a,b)=> (a[b]=obj[b],a),{});
// TEST
let elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
console.log( subset(elmo, ['color', 'height']) );
Use pick method of lodash library if you are already using.
var obj = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
https://lodash.com/docs/4.17.10#pick
The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
For example:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
const obj = {a:1, b:2, c:3, d:4}
const keys = ['a', 'c', 'f']
const picked = pick(obj,keys)
console.log(picked)
pick = (obj, keys) => {
return Object.assign({}, ...keys.map(key => ({
[key]: obj[key]
})))
}
const obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
const keys = ['a', 'c', 'f']
const picked = pick(obj, keys)
console.log(picked)
An Array of Objects
const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]
Making a subset of an object or objects can be achieved by destructuring the object this way.
const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Using the "with" statement with shorthand object literal syntax
Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.
var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output = {a,b,f}
}
console.log(output)
Pro: You don't have to type the property names twice.
Cons: The "with" statement is not recommended for many reasons.
Conclusion: It works great, but don't use it.
Just another way...
var elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
var subset = [elmo].map(x => ({
color: x.color,
height: x.height
}))[0]
You can use this function with an array of Objects =)
If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:
const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}
To add another esoteric way, this works aswell:
var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}
but you have to write the prop names twice.
How about:
function sliceObj(obj) {
var o = {}
, keys = [].slice.call(arguments, 1);
for (var i=0; i<keys.length; i++) {
if (keys[i] in obj) o[keys[i]] = obj[keys[i]];
}
return o;
}
var subset = sliceObj(elmo, 'color', 'height');
This works for me in Chrome console. Any problem with this?
var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}
convert arguments to array
use Array.forEach() to pick the property
Object.prototype.pick = function(...args) {
var obj = {};
args.forEach(k => obj[k] = this[k])
return obj
}
var a = {0:"a",1:"b",2:"c"}
var b = a.pick('1','2') //output will be {1: "b", 2: "c"}
Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.
let set = { a : 1, b : 2, c : 3 };
let subset = { a : null, b : null };
try {
Object.assign(Object.seal(subset), set);
} catch (e) {
console.log('its ok I meant to do that <(^.^)^');
}
console.log(subset);
I think this is your answer. (and everyone who is looking for it).
const object = { a: 5, b: 6, c: 7 };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }
Good-old Array.prototype.reduce:
const selectable = {a: null, b: null};
const v = {a: true, b: 'yes', c: 4};
const r = Object.keys(selectable).reduce((a, b) => {
return (a[b] = v[b]), a;
}, {});
console.log(r);
this answer uses the magical comma-operator, also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
if you want to get really fancy, this is more compact:
const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});
Putting it all together into a reusable function:
const getSelectable = function (selectable, original) {
return Object.keys(selectable).reduce((a, b) => (a[b] = original[b], a), {})
};
const r = getSelectable(selectable, v);
console.log(r);
I've got the same problem and solved it easily by using the following libs:
object.pick
https://www.npmjs.com/package/object.pick
pick({a: 'a', b: 'b', c: 'c'}, ['a', 'b'])
//=> {a: 'a', b: 'b'}
object.omit
https://www.npmjs.com/package/object.omit
omit({a: 'a', b: 'b', c: 'c'}, ['a', 'c'])
//=> { b: 'b' }
I know it isn't the cleanest, but it's simple and easy to understand.
function obj_multi_select(obj, keys){
let return_obj = {};
for (let k = 0; k < keys.length; k++){
return_obj[keys[k]] = obj[keys[k]];
};
return return_obj;
};
function splice()
{
var ret = new Object();
for(i = 1; i < arguments.length; i++)
ret[arguments[i]] = arguments[0][arguments[i]];
return ret;
}
var answer = splice(elmo, "color", "height");
Destructuring assignment with dynamic properties
This solution not only applies to your specific example but is more generally applicable:
const subset2 = (x, y) => ({[x]:a, [y]:b}) => ({[x]:a, [y]:b});
const subset3 = (x, y, z) => ({[x]:a, [y]:b, [z]:c}) => ({[x]:a, [y]:b, [z]:c});
// const subset4...etc.
const o = {a:1, b:2, c:3, d:4, e:5};
const pickBD = subset2("b", "d");
const pickACE = subset3("a", "c", "e");
console.log(
pickBD(o), // {b:2, d:4}
pickACE(o) // {a:1, c:3, e:5}
);
You can easily define subset4 etc. to take more properties into account.

How to get a subset of a javascript object's properties

Say I have an object:
elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
I want to make a new object with a subset of its properties.
// pseudo code
subset = elmo.slice('color', 'height')
//=> { color: 'red', height: 'unknown' }
How may I achieve this?
Using Object Destructuring and Property Shorthand
const object = { a: 5, b: 6, c: 7 };
const picked = (({ a, c }) => ({ a, c }))(object);
console.log(picked); // { a: 5, c: 7 }
From Philipp Kewisch:
This is really just an anonymous function being called instantly. All of this can be found on the Destructuring Assignment page on MDN. Here is an expanded form
let unwrap = ({a, c}) => ({a, c});
let unwrap2 = function({a, c}) { return { a, c }; };
let picked = unwrap({ a: 5, b: 6, c: 7 });
let picked2 = unwrap2({a: 5, b: 6, c: 7})
console.log(picked)
console.log(picked2)
Two common approaches are destructuring and conventional Lodash-like pick/omit implementation. The major practical difference between them is that destructuring requires a list of keys to be static, can't omit them, includes non-existent picked keys, i.e. it's inclusive. This may or not be desirable and cannot be changed for destructuring syntax.
Given:
var obj = { 'foo-bar': 1, bar: 2, qux: 3 };
The expected result for regular picking of foo-bar, bar, baz keys:
{ 'foo-bar': 1, bar: 2 }
The expected result for inclusive picking:
{ 'foo-bar': 1, bar: 2, baz: undefined }
Destructuring
Destructuring syntax allows to destructure and recombine an object, with either function parameters or variables.
The limitation is that a list of keys is predefined, they cannot be listed as strings, as described in the question. Destructuring becomes more complicated if a key is non-alphanumeric, e.g. foo-bar.
The upside is that it's performant solution that is natural to ES6.
The downside is that a list of keys is duplicated, this results in verbose code in case a list is long. Since destructuring duplicates object literal syntax in this case, a list can be copied and pasted as is.
IIFE
const subset = (({ 'foo-bar': foo, bar, baz }) => ({ 'foo-bar': foo, bar, baz }))(obj);
Temporary variables
const { 'foo-bar': foo, bar, baz } = obj;
const subset = { 'foo-bar': foo, bar, baz };
A list of strings
Arbitrary list of picked keys consists of strings, as the question requires. This allows to not predefine them and use variables that contain key names, ['foo-bar', someKey, ...moreKeys].
ECMAScript 2017 has Object.entries and Array.prototype.includes, ECMAScript 2019 has Object.fromEntries, they can be polyfilled when needed.
One-liners
Considering that an object to pick contains extra keys, it's generally more efficient to iterate over keys from a list rather than object keys, and vice versa if keys need to be omitted.
Pick (ES5)
var subset = ['foo-bar', 'bar', 'baz']
.reduce(function (obj2, key) {
if (key in obj) // line can be removed to make it inclusive
obj2[key] = obj[key];
return obj2;
}, {});
Omit (ES5)
var subset = Object.keys(obj)
.filter(function (key) {
return ['baz', 'qux'].indexOf(key) < 0;
})
.reduce(function (obj2, key) {
obj2[key] = obj[key];
return obj2;
}, {});
Pick (ES6)
const subset = ['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Omit (ES6)
const subset = Object.keys(obj)
.filter(key => ['baz', 'qux'].indexOf(key) < 0)
.reduce((obj2, key) => (obj2[key] = obj[key], obj2), {});
Pick (ES2019)
const subset = Object.fromEntries(
['foo-bar', 'bar', 'baz']
.filter(key => key in obj) // line can be removed to make it inclusive
.map(key => [key, obj[key]])
);
Omit (ES2019)
const subset = Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['baz', 'qux'].includes(key))
);
Reusable functions
One-liners can be represented as reusable helper functions similar to Lodash pick or omit, where a list of keys is passed through arguments, pick(obj, 'foo-bar', 'bar', 'baz').
JavaScript
const pick = (obj, ...keys) => Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
);
const inclusivePick = (obj, ...keys) => Object.fromEntries(
keys.map(key => [key, obj[key]])
);
const omit = (obj, ...keys) => Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key))
);
TypeScript
Credit goes to #Claude.
const pick = <T extends {}, K extends keyof T>(obj: T, ...keys: K[]) => (
Object.fromEntries(
keys
.filter(key => key in obj)
.map(key => [key, obj[key]])
) as Pick<T, K>
);
const inclusivePick = <T extends {}, K extends (string | number | symbol)>(
obj: T, ...keys: K[]
) => (
Object.fromEntries(
keys
.map(key => [key, obj[key as unknown as keyof T]])
) as {[key in K]: key extends keyof T ? T[key] : undefined}
)
const omit = <T extends {}, K extends keyof T>(
obj: T, ...keys: K[]
) =>(
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !keys.includes(key as K))
) as Omit<T, K>
)
I suggest taking a look at Lodash; it has a lot of great utility functions.
For example pick() would be exactly what you seek:
var subset = _.pick(elmo, ['color', 'height']);
fiddle
If you are using ES6 there is a very concise way to do this using destructuring. Destructuring allows you to easily add on to objects using a spread, but it also allows you to make subset objects in the same way.
const object = {
a: 'a',
b: 'b',
c: 'c',
d: 'd',
}
// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};
console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};
While it's a bit more verbose, you can accomplish what everyone else was recommending underscore/lodash for 2 years ago, by using Array.prototype.reduce.
var subset = ['color', 'height'].reduce(function(o, k) { o[k] = elmo[k]; return o; }, {});
This approach solves it from the other side: rather than take an object and pass property names to it to extract, take an array of property names and reduce them into a new object.
While it's more verbose in the simplest case, a callback here is pretty handy, since you can easily meet some common requirements, e.g. change the 'color' property to 'colour' on the new object, flatten arrays, etc. -- any of the things you need to do when receiving an object from one service/library and building a new object needed somewhere else. While underscore/lodash are excellent, well-implemented libs, this is my preferred approach for less vendor-reliance, and a simpler, more consistent approach when my subset-building logic gets more complex.
edit: es7 version of the same:
const subset = ['color', 'height'].reduce((a, e) => (a[e] = elmo[e], a), {});
edit: A nice example for currying, too! Have a 'pick' function return another function.
const pick = (...props) => o => props.reduce((a, e) => ({ ...a, [e]: o[e] }), {});
The above is pretty close to the other method, except it lets you build a 'picker' on the fly. e.g.
pick('color', 'height')(elmo);
What's especially neat about this approach, is you can easily pass in the chosen 'picks' into anything that takes a function, e.g. Array#map:
[elmo, grover, bigBird].map(pick('color', 'height'));
// [
// { color: 'red', height: 'short' },
// { color: 'blue', height: 'medium' },
// { color: 'yellow', height: 'tall' },
// ]
I am adding this answer because none of the answer used Comma operator.
It's very easy with destructuring assignment and , operator
const object = { a: 5, b: 6, c: 7 };
const picked = ({a,c} = object, {a,c})
console.log(picked);
One more solution:
var subset = {
color: elmo.color,
height: elmo.height
}
This looks far more readable to me than pretty much any answer so far, but maybe that's just me!
There is nothing like that built-in to the core library, but you can use object destructuring to do it...
const {color, height} = sourceObject;
const newObject = {color, height};
You could also write a utility function do it...
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
Libraries such as Lodash also have _.pick().
TypeScript solution:
function pick<T extends object, U extends keyof T>(
obj: T,
paths: Array<U>
): Pick<T, U> {
const ret = Object.create(null);
for (const k of paths) {
ret[k] = obj[k];
}
return ret;
}
The typing information even allows for auto-completion:
Credit to DefinitelyTyped for U extends keyof T trick!
TypeScript Playground
I want to mention that very good curation here:
pick-es2019.js
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
);
pick-es2017.js
Object.entries(obj)
.filter(([key]) => ['whitelisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
pick-es2015.js
Object.keys(obj)
.filter((key) => ['whitelisted', 'keys'].indexOf(key) >= 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
omit-es2019.js
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
);
omit-es2017.js
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
omit-es2015.js
Object.keys(obj)
.filter((key) => ['blacklisted', 'keys'].indexOf(key) < 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
You can use Lodash also.
var subset = _.pick(elmo ,'color', 'height');
Complementing, let's say you have an array of "elmo"s :
elmos = [{
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
},{
color: 'blue',
annoying: true,
height: 'known',
meta: { one: '1', two: '2'}
},{
color: 'yellow',
annoying: false,
height: 'unknown',
meta: { one: '1', two: '2'}
}
];
If you want the same behavior, using lodash, you would just:
var subsets = _.map(elmos, function(elm) { return _.pick(elm, 'color', 'height'); });
Destructuring into dynamically named variables is impossible in JavaScript as discussed in this question.
To set keys dynamically, you can use reduce function without mutating object as follows:
const getSubset = (obj, ...keys) => keys.reduce((a, c) => ({ ...a, [c]: obj[c] }), {});
const elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
const subset = getSubset(elmo, 'color', 'annoying')
console.log(subset)
Should note that you're creating a new object on every iteration though instead of updating a single clone. – mpen
below is a version using reduce with single clone (updating initial value passed in to reduce).
const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
acc[curr] = obj[curr]
return acc
}, {})
const elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)
Dynamic solution
['color', 'height'].reduce((a,b) => (a[b]=elmo[b],a), {})
let subset= (obj,keys)=> keys.reduce((a,b)=> (a[b]=obj[b],a),{});
// TEST
let elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
};
console.log( subset(elmo, ['color', 'height']) );
Use pick method of lodash library if you are already using.
var obj = { 'a': 1, 'b': '2', 'c': 3 };
_.pick(object, ['a', 'c']);
// => { 'a': 1, 'c': 3 }
https://lodash.com/docs/4.17.10#pick
The easiest way I found, which doesn't create unnecessary variables, is a function you can call and works identically to lodash is the following:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
For example:
pick(obj, keys){
return Object.assign({}, ...keys.map(key => ({ [key]: obj[key] })))
}
const obj = {a:1, b:2, c:3, d:4}
const keys = ['a', 'c', 'f']
const picked = pick(obj,keys)
console.log(picked)
pick = (obj, keys) => {
return Object.assign({}, ...keys.map(key => ({
[key]: obj[key]
})))
}
const obj = {
a: 1,
b: 2,
c: 3,
d: 4
}
const keys = ['a', 'c', 'f']
const picked = pick(obj, keys)
console.log(picked)
An Array of Objects
const aListOfObjects = [{
prop1: 50,
prop2: "Nothing",
prop3: "hello",
prop4: "What's up",
},
{
prop1: 88,
prop2: "Whatever",
prop3: "world",
prop4: "You get it",
},
]
Making a subset of an object or objects can be achieved by destructuring the object this way.
const sections = aListOfObjects.map(({prop1, prop2}) => ({prop1, prop2}));
Using the "with" statement with shorthand object literal syntax
Nobody has demonstrated this method yet, probably because it's terrible and you shouldn't do it, but I feel like it has to be listed.
var o = {a:1,b:2,c:3,d:4,e:4,f:5}
with(o){
var output = {a,b,f}
}
console.log(output)
Pro: You don't have to type the property names twice.
Cons: The "with" statement is not recommended for many reasons.
Conclusion: It works great, but don't use it.
Just another way...
var elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
var subset = [elmo].map(x => ({
color: x.color,
height: x.height
}))[0]
You can use this function with an array of Objects =)
If you want to keep more properties than the ones you want to remove, you could use the rest parameter syntax:
const obj = {
a:1,
b:2,
c:3,
d:4
};
const { a, ...newObj } = obj;
console.log(newObj); // {b: 2, c: 3, d: 4}
To add another esoteric way, this works aswell:
var obj = {a: 1, b:2, c:3}
var newobj = {a,c}=obj && {a,c}
// {a: 1, c:3}
but you have to write the prop names twice.
How about:
function sliceObj(obj) {
var o = {}
, keys = [].slice.call(arguments, 1);
for (var i=0; i<keys.length; i++) {
if (keys[i] in obj) o[keys[i]] = obj[keys[i]];
}
return o;
}
var subset = sliceObj(elmo, 'color', 'height');
This works for me in Chrome console. Any problem with this?
var { color, height } = elmo
var subelmo = { color, height }
console.log(subelmo) // {color: "red", height: "unknown"}
convert arguments to array
use Array.forEach() to pick the property
Object.prototype.pick = function(...args) {
var obj = {};
args.forEach(k => obj[k] = this[k])
return obj
}
var a = {0:"a",1:"b",2:"c"}
var b = a.pick('1','2') //output will be {1: "b", 2: "c"}
Like several on this thread I agree with evert that the most obvious old school way of doing this is actually the best available, however for fun let me provide one other inadvisable way of doing it in certain circumstances, say when you already have your subset defined and you want to copy properties to it from another object that contains a superset or intersecting set of its properties.
let set = { a : 1, b : 2, c : 3 };
let subset = { a : null, b : null };
try {
Object.assign(Object.seal(subset), set);
} catch (e) {
console.log('its ok I meant to do that <(^.^)^');
}
console.log(subset);
I think this is your answer. (and everyone who is looking for it).
const object = { a: 5, b: 6, c: 7 };
const subset = (({ a, c }) => ({ a, c }))(object);
console.log(subset); // { a: 5, c: 7 }
Good-old Array.prototype.reduce:
const selectable = {a: null, b: null};
const v = {a: true, b: 'yes', c: 4};
const r = Object.keys(selectable).reduce((a, b) => {
return (a[b] = v[b]), a;
}, {});
console.log(r);
this answer uses the magical comma-operator, also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator
if you want to get really fancy, this is more compact:
const r = Object.keys(selectable).reduce((a, b) => (a[b] = v[b], a), {});
Putting it all together into a reusable function:
const getSelectable = function (selectable, original) {
return Object.keys(selectable).reduce((a, b) => (a[b] = original[b], a), {})
};
const r = getSelectable(selectable, v);
console.log(r);
I've got the same problem and solved it easily by using the following libs:
object.pick
https://www.npmjs.com/package/object.pick
pick({a: 'a', b: 'b', c: 'c'}, ['a', 'b'])
//=> {a: 'a', b: 'b'}
object.omit
https://www.npmjs.com/package/object.omit
omit({a: 'a', b: 'b', c: 'c'}, ['a', 'c'])
//=> { b: 'b' }
I know it isn't the cleanest, but it's simple and easy to understand.
function obj_multi_select(obj, keys){
let return_obj = {};
for (let k = 0; k < keys.length; k++){
return_obj[keys[k]] = obj[keys[k]];
};
return return_obj;
};
function splice()
{
var ret = new Object();
for(i = 1; i < arguments.length; i++)
ret[arguments[i]] = arguments[0][arguments[i]];
return ret;
}
var answer = splice(elmo, "color", "height");
Destructuring assignment with dynamic properties
This solution not only applies to your specific example but is more generally applicable:
const subset2 = (x, y) => ({[x]:a, [y]:b}) => ({[x]:a, [y]:b});
const subset3 = (x, y, z) => ({[x]:a, [y]:b, [z]:c}) => ({[x]:a, [y]:b, [z]:c});
// const subset4...etc.
const o = {a:1, b:2, c:3, d:4, e:5};
const pickBD = subset2("b", "d");
const pickACE = subset3("a", "c", "e");
console.log(
pickBD(o), // {b:2, d:4}
pickACE(o) // {a:1, c:3, e:5}
);
You can easily define subset4 etc. to take more properties into account.

Categories