How to recursively map/restructure an object? - javascript

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.

Related

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

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' ]

How can I recursively replace the key name in an object?

I am trying to figure out how to use recursion to replace key names of an object with a new key name (and this includes key names inside of nested objects as well). I feel it has something to with the way I'm reassigning to newObj in my first if conditional statement. Any suggestions? Here is my code so far:
// 24. Find all keys in an object (and nested objects) by a provided name and rename
// them to a provided new name while preserving the value stored at that key.
const replaceKeysInObj = (obj, oldKey, newKey, newObj = {}) => {
for(let key in obj){
if (key === oldKey){
newObj[newKey] = obj[key]
}
if (typeof obj[key] === 'object'){
replaceKeysInObj(obj[key], oldKey, newKey);
}
else {
newObj[oldKey] = obj[key]
}
}
return newObj
}
var obj = {'e':{'e':'y'},'l': 'l','y':'e'};
console.log(replaceKeysInObj(obj, 'e', 'new'))
With some modifications to your approach
const replaceKeysInObj = (obj, oldKey, newKey, newObj = {}) => {
if (typeof obj !== "object") return obj;
for (let key in obj) {
newObj[key === oldKey ? newKey : key] = replaceKeysInObj(obj[key], oldKey, newKey);
}
return newObj;
};
const obj = { e: { e: "y" }, l: "l", y: "e" };
console.log(replaceKeysInObj(obj, "e", "new"));
const obj2 = { e: { e: "y" }, l: { e: "y" }, y: "e" };
console.log(replaceKeysInObj(obj2, "e", "new"));
Try this function:
function replaceKeysInObj(obj, oldKey, newKey) {
Object.keys(obj).map((key) => {
if(typeof obj[key] === 'object') {
replaceKeysInObj(obj[key], oldKey, newKey);
}
if(key === oldKey) {
obj[newKey] = obj[oldKey]
delete obj[oldKey];
}
});
return obj;
}
let obj = {'e':{'e':'y'},'l': 'l','y':'e'};
console.log(replaceKeysInObj(obj, 'e', 'new'))
If we think of this a little more generally, we can write a simple recursion. We can write a function to replace all the keys in an arbitrarily-nested object with the result of calling a function on the current keys. We could use this in various ways. If we wanted to convert all keys to upper-case, we would pass it k => k.toUpperCase(). Or for your case, we could write something like k => k == oldKey ? newKey : k. One implementation of this idea could look like this:
const replaceKey = (f) => (o) =>
Array .isArray (o)
? o .map (replaceKey (f))
: Object (o) === o
? Object .fromEntries (Object .entries (o) .map (([k, v]) => [f(k), replaceKey (f) (v)]))
: o
const replaceKeysInObj = (oldKey, newKey) =>
replaceKey (k => k == oldKey ? newKey : k)
const testCase = {foo: 1, bar: {baz: 2, qux: {corge: [{baz: 3}, {baz: 4}]}}}
console.log (
replaceKey (k => k.toUpperCase()) (testCase)
) //~> {FOO: 1, BAR: {BAZ: 2, QUX: {CORGE: [{BAZ: 3}, {BAZ: 4}]}}}
console.log (
replaceKeysInObj ('baz', 'grault') (testCase)
) //~> {foo: 1, bar: {grault: 2, qux: {corge: [{grault: 3}, {grault: 4}]}}}
.as-console-wrapper {max-height: 100% !important; top: 0}
Note that I changed the API of your function a bit. If you wanted the original signature, we could just write
const replaceKeysInObj = (obj, oldKey, newKey) =>
replaceKey (k => k == oldKey ? newKey : k) (obj)
But there is an advantage to how I wrote it, one that I take advantage of often. It lets us partially apply the oldKey and newKey to get a reusable function
const e2new = replaceKeysInObj ('e', 'new')
// later
e2new ({e: {e: 'y'}, l: 'l', y: 'e'}) //=> {new: {new: "y"}, l: "l", y:"e"}
But there's a general point here worth noting: it's often simpler to abstract at least a bit from our current problem, writing a more general solution that is configured with a function or two to fill in the details. Even if we eventually give up the abstraction and inline those configuration functions, it can help us see the problem more clearly.
if you want to change object keys name
i think this is working !
if (oldKey !== newKey) {
Object.defineProperty(obj, newKey, Object.getOwnPropertyDescriptor(obj, oldKey));
delete o[oldKey];
}
If you wish to return a new object with the replaced keys rather than mutating the object, as per the code you have posted, change it to the following:
const replaceKeysInObj = (obj, oldKey, newKey, newObj = {}) => {
for (let key in obj) {
if (key === oldKey) {
newObj[newKey] = obj[key];
} else {
newObj[key] = obj[key];
}
if (typeof obj[key] === 'object') {
newObj[newKey] = replaceKeysInObj(obj[key], oldKey, newKey);
}
}
return newObj;
};
var obj = {
'e': {
'e': 'y',
},
'l': 'l',
'y': 'e',
};
console.log(replaceKeysInObj(obj, 'e', 'new'));
// { new: { new: 'y' }, l: 'l', y: 'e' }
Based on answer of #Scott Sauyet I made one function. You can see example on CodeSandbox. Link for CodeSandbox.
export const replaceKeysInObj = (data, oldKey, newKey) => {
const replaceKey = (f) => (newdata) => {
if (Array.isArray(newdata)) return newdata.map(replaceKey(f));
if (Object(newdata) === newdata) {
return Object.fromEntries(
Object.entries(newdata).map(([key, value]) => [
f(key),
replaceKey(f)(value)
])
);
}
return data;
};
return replaceKey((key) => (key === oldKey ? newKey : key))(data);
};

Javascript hasOwnProperty for chain (nested) of properites? [duplicate]

I have a HUGE collection and I am looking for a property by key someplace inside the collection. What is a reliable way to get a list of references or full paths to all objects containing that key/index? I use jQuery and lodash if it helps and you can forget about infinite pointer recursion, this is a pure JSON response.
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "d");
// [o.c]
fn({ 'a': 1, 'b': 2, 'c': {'d':{'e':7}}}, "e");
// [o.c.d]
fn({ 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
// [o.cc,o.cc.dd]
fwiw lodash has a _.find function that will find nested objects that are two nests deep, but it seems to fail after that. (e.g. http://codepen.io/anon/pen/bnqyh)
This should do it:
function fn(obj, key) {
if (_.has(obj, key)) // or just (key in obj)
return [obj];
// elegant:
return _.flatten(_.map(obj, function(v) {
return typeof v == "object" ? fn(v, key) : [];
}), true);
// or efficient:
var res = [];
_.forEach(obj, function(v) {
if (typeof v == "object" && (v = fn(v, key)).length)
res.push.apply(res, v);
});
return res;
}
a pure JavaScript solution would look like the following:
function findNested(obj, key, memo) {
var i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (i === key) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
findNested(obj[i], key, memo);
}
}
}
return memo;
}
here's how you'd use this function:
findNested({'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}, 'd');
and the result would be:
[{x: 9}, {y: 9}]
this will deep search an array of objects (hay) for a value (needle) then return an array with the results...
search = function(hay, needle, accumulator) {
var accumulator = accumulator || [];
if (typeof hay == 'object') {
for (var i in hay) {
search(hay[i], needle, accumulator) == true ? accumulator.push(hay) : 1;
}
}
return new RegExp(needle).test(hay) || accumulator;
}
If you can write a recursive function in plain JS (or with combination of lodash) that will be the best one (by performance), but if you want skip recursion from your side and want to go for a simple readable code (which may not be best as per performance) then you can use lodash#cloneDeepWith for any purposes where you have to traverse a object recursively.
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
So, the callback you passes as the 2nd argument of _.cloneDeepWith will recursively traverse all the key/value pairs recursively and all you have to do is the operation you want to do with each. the above code is just a example of your case. Here is a working example:
var object = {
prop1: 'ABC1',
prop2: 'ABC2',
prop3: {
prop4: 'ABC3',
prop5Arr: [{
prop5: 'XYZ'
},
{
prop5: 'ABC4'
},
{
prop6: {
prop6NestedArr: [{
prop1: 'XYZ Nested Arr'
},
{
propFurtherNested: {key100: '100 Value'}
}
]
}
}
]
}
}
let findValuesDeepByKey = (obj, key, res = []) => (
_.cloneDeepWith(obj, (v,k) => {k==key && res.push(v)}) && res
)
console.log(findValuesDeepByKey(object, 'prop1'));
console.log(findValuesDeepByKey(object, 'prop5'));
console.log(findValuesDeepByKey(object, 'key100'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
With Deepdash you can pickDeep and then get paths from it, or indexate (build path->value object)
var obj = { 'aa': 1, 'bb': 2, 'cc': {'d':{'x':9}}, dd:{'d':{'y':9}}}
var cherry = _.pickDeep(obj,"d");
console.log(JSON.stringify(cherry));
// {"cc":{"d":{}},"dd":{"d":{}}}
var paths = _.paths(cherry);
console.log(paths);
// ["cc.d", "dd.d"]
paths = _.paths(cherry,{pathFormat:'array'});
console.log(JSON.stringify(paths));
// [["cc","d"],["dd","d"]]
var index = _.indexate(cherry);
console.log(JSON.stringify(index));
// {"cc.d":{},"dd.d":{}}
Here is a Codepen demo
Something like this would work, converting it to an object and recursing down.
function find(jsonStr, searchkey) {
var jsObj = JSON.parse(jsonStr);
var set = [];
function fn(obj, key, path) {
for (var prop in obj) {
if (prop === key) {
set.push(path + "." + prop);
}
if (obj[prop]) {
fn(obj[prop], key, path + "." + prop);
}
}
return set;
}
fn(jsObj, searchkey, "o");
}
Fiddle: jsfiddle
In case you don't see the updated answer from #eugene, this tweak allows for passing a list of Keys to search for!
// Method that will find any "message" in the Apex errors that come back after insert attempts
// Could be a validation rule, or duplicate record, or pagemessage.. who knows!
// Use in your next error toast from a wire or imperative catch path!
// message: JSON.stringify(this.findNested(error, ['message', 'stackTrace'])),
// Testing multiple keys: this.findNested({thing: 0, list: [{message: 'm'}, {stackTrace: 'st'}], message: 'm2'}, ['message', 'stackTrace'])
findNested(obj, keys, memo) {
let i,
proto = Object.prototype,
ts = proto.toString,
hasOwn = proto.hasOwnProperty.bind(obj);
if ('[object Array]' !== ts.call(memo)) memo = [];
for (i in obj) {
if (hasOwn(i)) {
if (keys.includes(i)) {
memo.push(obj[i]);
} else if ('[object Array]' === ts.call(obj[i]) || '[object Object]' === ts.call(obj[i])) {
this.findNested(obj[i], keys, memo);
}
}
}
return memo.length == 0 ? null : memo;
}
Here's how I did it:
function _find( obj, field, results )
{
var tokens = field.split( '.' );
// if this is an array, recursively call for each row in the array
if( obj instanceof Array )
{
obj.forEach( function( row )
{
_find( row, field, results );
} );
}
else
{
// if obj contains the field
if( obj[ tokens[ 0 ] ] !== undefined )
{
// if we're at the end of the dot path
if( tokens.length === 1 )
{
results.push( obj[ tokens[ 0 ] ] );
}
else
{
// keep going down the dot path
_find( obj[ tokens[ 0 ] ], field.substr( field.indexOf( '.' ) + 1 ), results );
}
}
}
}
Testing it with:
var obj = {
document: {
payload: {
items:[
{field1: 123},
{field1: 456}
]
}
}
};
var results = [];
_find(obj.document,'payload.items.field1', results);
console.log(results);
Outputs
[ 123, 456 ]
We use object-scan for data processing tasks. It's pretty awesome once you've wrapped your head around how to use it.
// const objectScan = require('object-scan');
const haystack = { a: { b: { c: 'd' }, e: { f: 'g' } } };
const r = objectScan(['a.*.*'], { joined: true, rtn: 'entry' })(haystack);
console.log(r);
// => [ [ 'a.e.f', 'g' ], [ 'a.b.c', 'd' ] ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan
There are plenty more examples on the website.
The shortest and simplest solution:
Array.prototype.findpath = function(item,path) {
return this.find(function(f){return item==eval('f.'+path)});
}

Convert square brackets notation to object

I have an object containing preformated attribute names of a serialized HTMLFormElement (2-dimensional):
var plain = {
id: 1,
'items[A][Z]': 2,
'items[B]': false,
'items[C][][A]': 1
}
I want to convert the object by creating the respective sub object(s):
var result = {
id: 1,
items: {
A: {Z:2},
B: false,
C: [ {A:1} ]
}
}
As far as I'm aware, this is a common practise - but I can't find more ressources on the subject. How is something like that usually called and what's the best way to convert plain to result?
Edit: I've updated the examples with an Array. This seems to be related and is also supported by the body-parser of express.
You could split the path and reduce the path by walking the given object. If no object exist, create a new property with the name, Later assign the value and delete the splitted property.
var plain = { id: 1, 'items[A][Z]': 2, 'items[B]': false };
Object.keys(plain).forEach(function (k) {
var path = k.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = path.pop();
if (path.length) {
path.reduce(function (o, p) {
return o[p] = o[p] || {};
}, plain)[last] = plain[k];
delete plain[k];
}
});
console.log(plain);
ES6
var plain = { id: 1, 'items[A][Z]': 2, 'items[B]': false };
Object.keys(plain).forEach(k => {
var path = k.replace(/\[/g, '.').replace(/\]/g, '').split('.'),
last = path.pop();
if (path.length) {
path.reduce((o, p) => o[p] = o[p] || {}, plain)[last] = plain[k];
delete plain[k];
}
});
console.log(plain);
You could use reduce() and filter() like this.
var plain = {
id: 1,
'items[A][Z]': 2,
'items[B]': false
}
var obj = {}
var result = Object.keys(plain).reduce(function(r, e) {
if (e.match(/\[(.*?)\]/gi)) {
var keys = e.split(/\[(.*?)\]/gi).filter(e => e != '');
keys.reduce(function(a, b, i) {
return (i != keys.length - 1) ? a[b] || (a[b] = {}) : a[b] = plain[e];
}, obj)
} else {
obj[e] = plain[e];
}
return r;
}, obj)
console.log(result)

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