get an object's property name using its value [duplicate] - javascript

This question already has answers here:
How to get a key in a JavaScript object by its value?
(31 answers)
Closed 2 years ago.
i have an object
const alpha = {
a: 'apple'
b: 'bubble'
}
the goal is to get the property name by using only the value, for example, I want to use the string apple to get the proberty name a in a form of a string.

The following simple implementation will log the keys for apple in the console.
const alpha = {
a: 'apple',
b: 'bubble'
}
Object.entries(alpha).map(
([key, value]) => {
if (value === 'apple'){
console.log(key);
}
}
)

I think you want to do something like this...
const alpha = {
a: 'apple',
b: 'bubble'
};
const selectKeys = (object, selector) => {
let results = [];
if (object != null) {
results = Object.entries(object)
.filter((e) => selector(e[1], e[0], e))
.map((e) => e[0]);
}
return results;
};
const keys = selectKeys(alpha, (value) => value === 'apple');
console.log(keys);
This will just select all keys where the selector expression returns true. For your case thats just the name. Notice that this returns an array, because multiple keys can be returned. To get the first key simply use keys[0] or whatever.
You could get fancier and add higher order functions to make your selectors easier to read as well.
const byValue = (value) => (v) => v === value;
const a = selectKeys(object, byValue('apple'));
const a = selectKeys(object, byValue('bubble'));

You would return a list of keys who's value matches the provided value. If you wanted the first match, just shift the value off the beginning of the result.
const alpha = {
a: 'apple',
b: 'bubble'
};
const keysForValue = (obj, value) =>
Object.entries(obj)
.filter(([, val]) => val === value)
.map(([key]) => key);
console.log('Key:', keysForValue(alpha, 'bubble').shift()); // Key: b
.as-console-wrapper { top: 0; max-height: 100% !important; }

Try the below approach,
const alpha = {
a: 'apple',
b: 'bubble',
c: 'bubble',
d: 'orange',
e: 'bubble'
}
const findKeyByValue = (obj, value) => {
const arr = [];
for (const prop in obj) {
if(obj[prop] === value){
arr.push(prop);
}
}
return arr;
};
findKeyByValue(alpha, 'bubble'); //[ 'b', 'c', 'e' ]

Related

How to update an object value in array of objects when the keys are same

I have an Array of objects and one object
const filterArray = [{bestTimeToVisit: 'Before 10am'}, {bestDayToVisit: Monday}]
This values are setting in a reducer and the payload will be like
{bestTimeToVisit: 'After 10am'}
or
{bestDayToVisit: Tuesday}.
So what I need is when I get a payload {bestTimeToVisit: 'After 10am'} and if bestTimeToVisit not in filterList array, then add this value to the filterList array.
And if bestTimeToVisit already in the array with different value, then replace the value of that object with same key
if(filterArray.hasOwnProperty("bestTimeToVisit")) {
filterArray["bestTimeToVisit"] = payload["bestTimeToVisit"];
} else {
filterArray.push({"bestTimeToVisit": payload["bestTimeToVisit"]});
}
I convert the object array into a regular object and then back into an object array. makes things less complicated. I'm making the assumption each object coming back only has one key/value and that order doesnt matter.
const objectArraytoObject = (arr) =>
arr.reduce((acc, item) => {
const key = [Object.keys(item)[0]];
return { ...acc, [key]: item[key] };
}, {});
const newValues = [{ someKey: 'something' }, { bestDayToVisit: 'Tuesday' }];
const filterArray = [
{ bestTimeToVisit: 'Before 10am' },
{ bestDayToVisit: 'Monday' },
];
const newValuesObj = objectArraytoObject(newValues);
const filterObj = objectArraytoObject(filterArray);
const combined = { ...filterObj, ...newValuesObj };
const combinedToArray = Object.keys(combined).map((key) => ({
[key]: combined[key],
}));
console.log(combinedToArray);
Need to iterate over the array and find objects that satisfy for modification or addition if none are found.
function checkReduced(filterrray,valueToCheck="After 10am"){
let isNotFound =true;
for(let timeItem of filterrray) {
if(timeItem.bestTimeToVisit && timeItem.bestTimeToVisit !== valueToCheck) {
timeItem.bestTimeToVisit=valueToCheck;
isNotFound=false;
break;
}
}
if(isNotFound){filterrray.push({bestTimeToVisit:valueToCheck})}
}
const filterArray = [{bestDayToVisit: "Monday"}];
checkReduced(filterArray,"After 9am");//calling the function
const updateOrAdd = (arr, newItem) => {
// get the new item key
const newItemKey = Object.keys(newItem)[0];
// get the object have the same key
const find = arr.find(item => Object.keys(item).includes(newItemKey));
if(find) { // the find object is a reference type
find[newItemKey] = newItem[newItemKey]; // update the value
} else {
arr.push(newItem); // push new item if there is no object have the same key
}
return arr;
}
// tests
updateOrAdd([{ a: 1 }], { b: 2 }) // => [{ a: 1 }, { b: 2 }]
updateOrAdd([{ a: 1 }], { a: 2 }) // => [{ a: 2 }]

How to recursively map/restructure an object?

I would like an array of objects with all object keys from a nested object. I wrote a recursive function to do this however at the point that the function is recalled it is not going through the object as expected but rather sending back an index infinitely.
let array = [];
const findKeys = (ob) => {
let id = 0;
let keys = Object.keys(ob);
for (let i = 0; i < keys.length; i++) {
let object = {
id: id,
label: keys[i],
};
array.push(object);
id ++;
findKeys(ob[keys[i]]);
}
return array;
};
let newArray = findKeys(data);
console.log(newArray);
example data structure:
const data = {a: {
b: {
c: {
foo: 'bar'
}
}
}}
You need to check to see if you have an object before you do the next recursive call. You also are resetting id so you are going to have the ids repeated (maybe you want that?) and you are using a global for the array so it can not be used more than once.
You are going to want something like:
function getKeys(obj) {
const array = [];
let id = 0;
function loop(obj) {
Object.entries(obj).forEach(entry => {
array.push({
id: ++id,
label: entry[0],
});
if(entry[1] != null && entry[1].constructor.name === "Object") {
loop(entry[1]);
}
});
}
loop(obj);
return array;
}
const obj1 = { a: 1, b: 'bar' };
console.log(getKeys(obj1));
const obj2 = { a: 1, b: { c: 'bar' } };
console.log(getKeys(obj2));
some thing like that
see also Check that value is object literal?
const data = { a: { b: { c: { foo: 'bar' } } }}
const isObject = el => (Object.prototype.toString.call(el) === '[object Object]')
const findKeys = obj =>
{
let arr = []
, id = 0
;
getKeys(obj)
return arr
function getKeys(o)
{
Object.keys(o).forEach(key =>
{
arr.push({ id:id++, label:key })
if (isObject(o[key]))
getKeys(o[key])
})
}
}
console.log( findKeys(data) )
.as-console-wrapper {max-height: 100%!important;top:0 }
Perhaps you may do like
var data = {a: {
b: {
c: {
foo: 'bar',
arr: [1,2,3,4]
}
}
}};
function getAllKeys(obj){
var keys = (typeof obj === "object") && (obj !== null) && Object.keys(obj);
return !!keys ? keys.reduce((r,k) => r.concat(getAllKeys(obj[k])),keys)
: [];
};
var res = getAllKeys(data);
console.log(JSON.stringify(res));
Here is a simple technique, using a fairly generic, depth-first, key-collecting traversal, followed by a mapping to add the indices:
const flattenKeys = (o) =>
Object (o) === o
? Object .entries (o) .flatMap (([k, v]) => [k, ...flattenKeys (v)])
: []
const getKeys = (o) =>
flattenKeys (o) .map ((label, id) => ({label, id}))
const data = {a: {b: {c: {foo: 'bar'}}}}
console .log (getKeys (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
If you wanted a breadth-first traversal it wouldn't be much harder.
This separation of key collection and index generation makes the code much simpler to my mind.

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.

Combine arrays of objects by object index [duplicate]

This question already has an answer here:
How to merge each object within arrays by index?
(1 answer)
Closed 3 months ago.
I am filtering an array for every value the is the same as the key provided. Im certain there is a one shot reduce method someone better than me can condense this down to, but alas filter map filter map.
So I submit to an array an object that says [{k:v}, {k2:otherv}] and find all the elements that are not that and then return those object keys.
The code below returns:
[
{k: v1},
{k: v2},
{k: v3}
]
[
{k2: v4},
{k2: v5},
{k2: v6}
]
]
And obviously to map over it correctly id like it to look like
[{k:v1, k2:v4}, {k:v2,k2:v5}, {k:v3, k2:v6}]
I've tried several examples from:
How can I merge two object arrays by index in JavaScript?
and
Combine same-index objects of two arrays
but short of writing every object key possible into each of these, none of what I've tried works.
const blogkeys = cont
.filter((k) => k.type === "blogs")
.map(({ key, content }) => {
if (key.includes(".")) {
let objkey = key.substr(key.indexOf(".") + 1, key.length);
let obj = { [objkey]: content };
let arrName = key.substr(0, key.indexOf("."));
let pushedObj = { [arrName]: [{ ...obj }] };
return pushedObj;
} else {
let obj = { [key]: content };
return obj;
}
});
this creates the keys we are looking for in the parent array
const everyOtherBlog = blogkeys.map((blogkey) => {
const returned = blogs
.filter(
(f) =>
!JSON.stringify(f).includes(
JSON.stringify(blogkey).replace("{", "").replace("}", "")
)
)
.map(({ _doc }) => {
let obj = {};
Object.keys(_doc)
.filter((f) => f === Object.keys(blogkey)[0])
.map((a) => {
obj = Object.assign(obj, { [a]: _doc[a] });
return obj;
});
return obj[0];
});
return returned;
});
This returns the data set you see.
Here is what blogkeys looks like :
[0] [
[0] { title: ' stuff' },
[0] {
[0] p1: ' stuff '
[0] }
[0] ]
which is made from
{
[0] _id: '606a4049d4812928986afc10',
[0] contentId: '60443ced4e233336f8306b5b',
[0] type: 'blogs',
[0] key: 'title',
[0] content: 'stuff'
[0] },
and a blog looks something like
{
title: '',
p1:''
}
Everyone here provided alot of cool stuff that ended up not helping me because of how i was feeding the data in, when i fixed that i realized i didnt need any fancy zips just good old object.fromEntries. Ill leave this up though cause some of these are very interesting.
Any help would be great
two arrays
You can use map to implement zip and then map again to perform your tranform. This solution works for only two input arrays -
const zip = (a, b) =>
a.map((x, i) => [x, b[i]])
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const result =
zip(foo, bar).map(o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
[{"a":1,"b":4},{"a":2,"b":5},{"a":3,"b":6}]
many arrays, any size
Above, you will run into strange output if a or b is longer than the other. I think a better approach is to use generators though. It works for any number of input arrays of any size -
const iter = t =>
t?.[Symbol.iterator]()
function* zip (...its)
{ let r, g = its.map(iter)
while (true)
{ r = g.map(it => it.next())
if (r.some(v => v.done)) return
yield r.map(v => v.value)
}
}
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const qux =
[{c:7},{c:8}]
const result =
Array.from(zip(foo, bar, qux), o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
This does the zipping and transformation in a single pass, without the need map afterward -
[{"a":1,"b":4,"c":7},{"a":2,"b":5,"c":8}]
without generators
If you don't like generators but still want the flexibility offered by the solution above, we can write a simple zip2 -
const zip2 = ([a, ...nexta], [b, ...nextb]) =>
a == null || b == null
? [] // empty
: [ [a, b], ...zip2(nexta, nextb) ] // recur
And then the variadiac zip which accepts any amount of arrays of any size -
const zip = (t, ...more) =>
more.length
? zip2(t, zip(...more)).map(([a, b]) => [a, ...b]) // flatten
: t.map(a => [a]) // singleton
Now we can zip any amount of arrays -
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const qux =
[{c:7},{c:8}]
const result =
zip(foo, bar, qux).map(o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
Expand the snippet below to verify the result in your own browser -
const zip2 = ([a, ...nexta], [b, ...nextb]) =>
a == null || b == null
? []
: [ [a, b], ...zip2(nexta, nextb) ]
const zip = (t, ...more) =>
more.length
? Array.from(zip2(t, zip(...more)), ([a, b]) => [a, ...b])
: t.map(a => [a])
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const qux =
[{c:7},{c:8}]
const result =
zip(foo, bar, qux).map(o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
[{"a":1,"b":4,"c":7},{"a":2,"b":5,"c":8}]
You can try this too with map and reduce, this is just another alternative
function merge(...args) {
// finding highest length Array to not skip missing elements from other arrays
// for skipping missing elements use "acc.length < ele.length"
const maxArray = args.reduce((acc, ele) => acc.length > ele.length ? acc : ele);
//Iterating over highest length array
return maxArray.map((ele, index) =>
//merging all the instances in arrays with same index
args.reduce((acc, group) => Object.assign(acc, group[index]), {})
);
}
merge([ {k: 'v1'}, {k: 'v2'}, {k: 'v3'} ], [ {k2: 'v4'}, {k2: 'v5'}, {k2: 'v6'} ]);
// [{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k":"v3","k2":"v6"}]
merge([ {k: 'v1'}, {k: 'v2'}], [ {k2: 'v4'}, {k2: 'v5'}, {k2: 'v6'} ])
// [{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k2":"v6"}]
merge([ {k: 'v1'}, {k: 'v2'}, {k: 'v3'} ], [ {k2: 'v4'}, {k2: 'v5'}])
//[{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k":"v3"}]
Here's a fairly straightforward solution using .reduce() that will accept any number of arrays of various lengths.
const
foo = [{ a: 1 }, { a: 2 }, { a: 3 }],
bar = [{ b: 4 }, { b: 5 }, { b: 6 }],
qux = [{ c: 7 }, { c: 8 }],
zip = (...arrs) =>
arrs.reduce((a, arr) => {
arr.forEach((x, i) => Object.assign((a[i] = a[i] || {}), x));
// or using logical nullish assignment
// arr.forEach((x, i) => Object.assign((a[i] ??= {}), x));
return a;
}, []);
result = zip(foo, bar, qux);
console.log(JSON.stringify(result))
// [{ a: 1, b: 4, c: 7 }, { a: 2, b: 5, c: 8 }, { a: 3, b: 6 }]
I wanted to share what I ended up doing cause it worked well with both nested arrays and simple object arrays and is formatted for getting info straight from an await from mongo db, sadly its just a filter map tho.
blog obj is
{
title:"stuff",
p1:"stuff"
}
and the return is the zipped array.
const everyOtherBlog = Object.values(blogObj).map((val) => {
const b = blogs
.filter((f) => !JSON.stringify(f).includes(val))
.map(({ _doc }) => {
const keys = Object.keys(_doc).filter((k) =>
Object.keys(blogObj).includes(k)
);
const entryObj = Object.fromEntries(keys.map((k) => [k, _doc[k]]));
return entryObj;
});
return b[0];
});

JavaScript: filter() for Objects

ECMAScript 5 has the filter() prototype for Array types, but not Object types, if I understand correctly.
How would I implement a filter() for Objects in JavaScript?
Let's say I have this object:
var foo = {
bar: "Yes"
};
And I want to write a filter() that works on Objects:
Object.prototype.filter = function(predicate) {
var result = {};
for (key in this) {
if (this.hasOwnProperty(key) && !predicate(this[key])) {
result[key] = this[key];
}
}
return result;
};
This works when I use it in the following demo, but when I add it to my site that uses jQuery 1.5 and jQuery UI 1.8.9, I get JavaScript errors in FireBug.
Object.prototype.filter = function(predicate) {
var result = {};
for (key in this) {
if (this.hasOwnProperty(key) && !predicate(this[key])) {
console.log("copying");
result[key] = this[key];
}
}
return result;
};
var foo = {
bar: "Yes",
moo: undefined
};
foo = foo.filter(function(property) {
return typeof property === "undefined";
});
document.getElementById('disp').innerHTML = JSON.stringify(foo, undefined, ' ');
console.log(foo);
#disp {
white-space: pre;
font-family: monospace
}
<div id="disp"></div>
First of all, it's considered bad practice to extend Object.prototype. Instead, provide your feature as stand-alone function, or if you really want to extend a global, provide it as utility function on Object, just like there already are Object.keys, Object.assign, Object.is, ...etc.
I provide here several solutions:
Using reduce and Object.keys
As (1), in combination with Object.assign
Using map and spread syntax instead of reduce
Using Object.entries and Object.fromEntries
1. Using reduce and Object.keys
With reduce and Object.keys to implement the desired filter (using ES6 arrow syntax):
Object.filter = (obj, predicate) =>
Object.keys(obj)
.filter( key => predicate(obj[key]) )
.reduce( (res, key) => (res[key] = obj[key], res), {} );
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1);
console.log(filtered);
Note that in the above code predicate must be an inclusion condition (contrary to the exclusion condition the OP used), so that it is in line with how Array.prototype.filter works.
2. As (1), in combination with Object.assign
In the above solution the comma operator is used in the reduce part to return the mutated res object. This could of course be written as two statements instead of one expression, but the latter is more concise. To do it without the comma operator, you could use Object.assign instead, which does return the mutated object:
Object.filter = (obj, predicate) =>
Object.keys(obj)
.filter( key => predicate(obj[key]) )
.reduce( (res, key) => Object.assign(res, { [key]: obj[key] }), {} );
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1);
console.log(filtered);
3. Using map and spread syntax instead of reduce
Here we move the Object.assign call out of the loop, so it is only made once, and pass it the individual keys as separate arguments (using the spread syntax):
Object.filter = (obj, predicate) =>
Object.assign(...Object.keys(obj)
.filter( key => predicate(obj[key]) )
.map( key => ({ [key]: obj[key] }) ) );
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, score => score > 1);
console.log(filtered);
4. Using Object.entries and Object.fromEntries
As the solution translates the object to an intermediate array and then converts that back to a plain object, it would be useful to make use of Object.entries (ES2017) and the opposite (i.e. create an object from an array of key/value pairs) with Object.fromEntries (ES2019).
It leads to this "one-liner" method on Object:
Object.filter = (obj, predicate) =>
Object.fromEntries(Object.entries(obj).filter(predicate));
// Example use:
var scores = {
John: 2, Sarah: 3, Janet: 1
};
var filtered = Object.filter(scores, ([name, score]) => score > 1);
console.log(filtered);
The predicate function gets a key/value pair as argument here, which is a bit different, but allows for more possibilities in the predicate function's logic.
Never ever extend Object.prototype.
Horrible things will happen to your code. Things will break. You're extending all object types, including object literals.
Here's a quick example you can try:
// Extend Object.prototype
Object.prototype.extended = "I'm everywhere!";
// See the result
alert( {}.extended ); // "I'm everywhere!"
alert( [].extended ); // "I'm everywhere!"
alert( new Date().extended ); // "I'm everywhere!"
alert( 3..extended ); // "I'm everywhere!"
alert( true.extended ); // "I'm everywhere!"
alert( "here?".extended ); // "I'm everywhere!"
Instead create a function that you pass the object.
Object.filter = function( obj, predicate) {
let result = {}, key;
for (key in obj) {
if (obj.hasOwnProperty(key) && !predicate(obj[key])) {
result[key] = obj[key];
}
}
return result;
};
Solution in Vanilla JS from year 2020.
let romNumbers={'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M':1000}
You can filter romNumbers object by key:
const filteredByKey = Object.fromEntries(
Object.entries(romNumbers).filter(([key, value]) => key === 'I') )
// filteredByKey = {I: 1}
Or filter romNumbers object by value:
const filteredByValue = Object.fromEntries(
Object.entries(romNumbers).filter(([key, value]) => value === 5) )
// filteredByValue = {V: 5}
If you're willing to use underscore or lodash, you can use pick (or its opposite, omit).
Examples from underscore's docs:
_.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
// {name: 'moe', age: 50}
Or with a callback (for lodash, use pickBy):
_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
return _.isNumber(value);
});
// {age: 50}
ES6 approach...
Imagine you have this object below:
const developers = {
1: {
id: 1,
name: "Brendan",
family: "Eich"
},
2: {
id: 2,
name: "John",
family: "Resig"
},
3: {
id: 3,
name: "Alireza",
family: "Dezfoolian"
}
};
Create a function:
const filterObject = (obj, filter, filterValue) =>
Object.keys(obj).reduce((acc, val) =>
(obj[val][filter] === filterValue ? acc : {
...acc,
[val]: obj[val]
}
), {});
And call it:
filterObject(developers, "name", "Alireza");
and will return:
{
1: {
id: 1,
name: "Brendan",
family: "Eich"
},
2: {
id: 2,
name: "John",
family: "Resig"
}
}
As patrick already stated this is a bad idea, as it will almost certainly break any 3rd party code you could ever wish to use.
All libraries like jquery or prototype will break if you extend Object.prototype, the reason being that lazy iteration over objects (without hasOwnProperty checks) will break since the functions you add will be part of the iteration.
Given
object = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};
keys = ['firstname', 'age'];
then :
keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
// {firstname:'abd', age: 16}
// Helper
function filter(object, ...keys) {
return keys.reduce((result, key) => ({ ...result, [key]: object[key] }), {});
};
//Example
const person = {firstname: 'abd', lastname:'tm', age:16, school:'insat'};
// Expected to pick only firstname and age keys
console.log(
filter(person, 'firstname', 'age')
)
Plain ES6:
var foo = {
bar: "Yes"
};
const res = Object.keys(foo).filter(i => foo[i] === 'Yes')
console.log(res)
// ["bar"]
How about:
function filterObj(keys, obj) {
const newObj = {};
for (let key in obj) {
if (keys.includes(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
Or...
function filterObj(keys, obj) {
const newObj = {};
Object.keys(obj).forEach(key => {
if (keys.includes(key)) {
newObj[key] = obj[key];
}
});
return newObj;
}
I have created an Object.filter() which does not only filter by a function, but also accepts an array of keys to include. The optional third parameter will allow you to invert the filter.
Given:
var foo = {
x: 1,
y: 0,
z: -1,
a: 'Hello',
b: 'World'
}
Array:
Object.filter(foo, ['z', 'a', 'b'], true);
Function:
Object.filter(foo, function (key, value) {
return Ext.isString(value);
});
Code
Disclaimer: I chose to use Ext JS core for brevity. Did not feel it was necessary to write type checkers for object types as it was not part of the question.
// Helper function
function print(obj) {
document.getElementById('disp').innerHTML += JSON.stringify(obj, undefined, ' ') + '<br />';
console.log(obj);
}
Object.filter = function (obj, ignore, invert) {
let result = {}; // Returns a filtered copy of the original list
if (ignore === undefined) {
return obj;
}
invert = invert || false;
let not = function(condition, yes) { return yes ? !condition : condition; };
let isArray = Ext.isArray(ignore);
for (var key in obj) {
if (obj.hasOwnProperty(key) &&
!(isArray && not(!Ext.Array.contains(ignore, key), invert)) &&
!(!isArray && not(!ignore.call(undefined, key, obj[key]), invert))) {
result[key] = obj[key];
}
}
return result;
};
let foo = {
x: 1,
y: 0,
z: -1,
a: 'Hello',
b: 'World'
};
print(Object.filter(foo, ['z', 'a', 'b'], true));
print(Object.filter(foo, (key, value) => Ext.isString(value)));
#disp {
white-space: pre;
font-family: monospace
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/extjs/4.2.1/builds/ext-core.min.js"></script>
<div id="disp"></div>
My opinionated solution:
function objFilter(obj, filter, nonstrict){
r = {}
if (!filter) return {}
if (typeof filter == 'string') return {[filter]: obj[filter]}
for (p in obj) {
if (typeof filter == 'object' && nonstrict && obj[p] == filter[p]) r[p] = obj[p]
else if (typeof filter == 'object' && !nonstrict && obj[p] === filter[p]) r[p] = obj[p]
else if (typeof filter == 'function'){ if (filter(obj[p],p,obj)) r[p] = obj[p]}
else if (filter.length && filter.includes(p)) r[p] = obj[p]
}
return r
}
Test cases:
obj = {a:1, b:2, c:3}
objFilter(obj, 'a') // returns: {a: 1}
objFilter(obj, ['a','b']) // returns: {a: 1, b: 2}
objFilter(obj, {a:1}) // returns: {a: 1}
objFilter(obj, {'a':'1'}, true) // returns: {a: 1}
objFilter(obj, (v,k,o) => v%2===1) // returns: {a: 1, c: 3}
https://gist.github.com/bernardoadc/872d5a174108823159d845cc5baba337
var foo = {
bar: "Yes",
pipe: "No"
};
const ret = Object.entries(foo).filter(([key, value])=> value === 'Yes');
https://masteringjs.io/tutorials/fundamentals/filter-object
If you have Symbol properties in your object, that should be filtered too, you can not use: Object.keys Object.entries Object.fromEntries, ... because:
Symbol keys are not enumerable !
You could use Reflect.ownKeys and filter keys in reduce
Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && {...a, [k]: o[k]} || a, {});
(Open DevTools for log output - Symbols are not logged on Stackoverflow UI)
const bKey = Symbol('b_k');
const o = {
a: 1,
[bKey]: 'b',
c: [1, 3],
[Symbol.for('d')]: 'd'
};
const allow = ['a', bKey, Symbol.for('d')];
const z1 = Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && {...a, [k]: o[k]} || a, {});
console.log(z1); // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
console.log(bKey in z1) // true
console.log(Symbol.for('d') in z1) // true
This is equal to this
const z2 = Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && Object.assign(a, {[k]: o[k]}) || a, {});
const z3 = Reflect.ownKeys(o).reduce((a, k) => allow.includes(k) && Object.defineProperty(a, k, {value: o[k]}) || a, {});
console.log(z2); // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
console.log(z3); // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
Wrapped in a filter() function, an optional target object could be passed
const filter = (o, allow, t = {}) => Reflect.ownKeys(o).reduce(
(a, k) => allow.includes(k) && {...a, [k]: o[k]} || a,
t
);
console.log(filter(o, allow)); // {a: 1, Symbol(b_k): "b", Symbol(d): "d"}
console.log(filter(o, allow, {e: 'e'})); // {a: 1, e: "e", Symbol(b_k): "b", Symbol(d): "d"}
You could also do something like this where you are filtering on the entries to find the key provided and return the value
let func = function(items){
let val
Object.entries(this.items).map(k => {
if(k[0]===kind){
val = k[1]
}
})
return val
}
If you wish to mutate the same object rather than create a new one.
The following example will delete all 0 or empty values:
const sev = { a: 1, b: 0, c: 3 };
const deleteKeysBy = (obj, predicate) =>
Object.keys(obj)
.forEach( (key) => {
if (predicate(obj[key])) {
delete(obj[key]);
}
});
deleteKeysBy(sev, val => !val);
Like everyone said, do not screw around with prototype. Instead, simply write a function to do so. Here is my version with lodash:
import each from 'lodash/each';
import get from 'lodash/get';
const myFilteredResults = results => {
const filteredResults = [];
each(results, obj => {
// filter by whatever logic you want.
// sample example
const someBoolean = get(obj, 'some_boolean', '');
if (someBoolean) {
filteredResults.push(obj);
}
});
return filteredResults;
};
If you don't need the original object, this is a simple, very boring answer that doesn't waste memory:
const obj = {'a': 'want this', 'b': 'want this too', 'x': 'remove this'}
const keep = new Set(['a', 'b', 'c'])
function filterObject(obj, keep) {
Object.keys(obj).forEach(key => {
if (!keep.has(key)) {
delete obj[key]
}
})
}
If you're only filtering a small number of objects, and your objects don't have many keys, you might not want to bother with constructing a Set, in which case use array.includes instead of set.has.
I just wanted to add the way that I do it because it saves me creating extra functions, I think is cleaner and I didn't see this answer:
let object = {a: 1, b: 2, c: 3};
[object].map(({a,c}) => ({a,c}))[0]; // {a:1, c:2}
The cool thing is that also works on arrays of objects:
let object2 = {a: 4, b: 5, c: 6, d: 7};
[object, object2].map(({a,b,c,d}) => ({a,c})); //[{"a":1,"c":3},{"a":4,"c":6}]
[object, object2].map(({a,d}) => ({a,d})); //[{"a":1,"d":undefined},{"a":4,"d":7}]
In these cases I use the jquery $.map, which can handle objects. As mentioned on other answers, it's not a good practice to change native prototypes (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#Bad_practice_Extension_of_native_prototypes)
Below is an example of filtering just by checking some property of your object. It returns the own object if your condition is true or returns undefined if not. The undefined property will make that record disappear from your object list;
$.map(yourObject, (el, index)=>{
return el.yourProperty ? el : undefined;
});

Categories