How to avoid 'undefined' errors in nested objects [duplicate] - javascript

This question already has answers here:
Access Javascript nested objects safely
(14 answers)
Closed 4 years ago.
I'm looking for some good strategies for avoiding errors in JavaScript when using dot notation to call the children of children in objects that may or may not exist.
At the bottom of the code snippet below is an example of a solution that works, but is inelegant (at best).
It would be great to see some native JavaScript solutions or even external libraries that can help avoid this kind of error.
const object1 = {
foo: {
bar: {
baz: 'payload'
}
}
};
const object2 = {};
const array = [object1, object2];
// this will fail on object2 because obj.foo is undefined
array.forEach(obj => {
if (obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
} else {
console.log('undefined');
}
}
);
// this will work, but it's horrible to write all those nested if statements.
array.forEach(obj => {
if (obj) {
if (obj.foo) {
if (obj.foo.bar) {
if (obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
}
}
}
} else {
console.log('undefinded');
}
}
);

Lodash already did it for us: https://lodash.com/docs#get
const object = { 'a': [{ 'b': { 'c': 3 } }] };
_.get(object, 'a[0].b.c');
// => 3
_.get(object, ['a', '0', 'b', 'c']);
// => 3
_.get(object, 'a.b.c', 'default');
// => 'default'

No sure if that's enough of an improvement but you can use a single if statement with the following condition:
(obj && obj.foo && obj.foo.bar && obj.foo.bar.baz)
This will check if obj.foo.bar.baz exists.
const array=[{foo:{bar:{baz:'payload'}}},{}]
array.forEach(obj => {
if (obj && obj.foo && obj.foo.bar && obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
} else {
console.log('undefined');
}
});

You could chain all checks with logical AND &&.
const
object1 = { foo: { bar: { baz: 'payload' } } },
object2 = {},
array = [object1, object2];
array.forEach(obj => {
if (obj && obj.foo && obj.foo.bar && obj.foo.bar.baz) {
console.log(obj.foo.bar.baz);
} else {
console.log('undefined');
}
});
For an automatic check, you could take an array of keys and return either the value or undefined.
const
getValue = (object, keys) => keys.reduce((o, k) => (o || {})[k], object),
object1 = { foo: { bar: { baz: 'payload' } } },
object2 = {},
array = [object1, object2];
array.forEach(obj => console.log(getValue(obj, ['foo', 'bar', 'baz'])));

Just to share my two cents:
Some time ago I made a function that allowed to safely access deep properties in javascript using proxies:
// Here is where the magic happens
function optional(obj, evalFunc, def) {
// Our proxy handler
const handler = {
// Intercept all property access
get: function(target, prop, receiver) {
const res = Reflect.get(...arguments);
// If our response is an object then wrap it in a proxy else just return
return typeof res === "object" ? proxify(res) : res != null ? res : def;
}
};
const proxify = target => {
return new Proxy(target, handler);
};
// Call function with our proxified object
return evalFunc(proxify(obj, handler));
}
const obj = {
items: [{
hello: "Hello"
}]
};
console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, {
a: 1
})); // => { a: 1 }
Also, I wrote an article on this for further reference.

Related

How to find the key of a value in a nested object recursively

I want to find the key of a value in a Javascript nested object with recursion.
Here is my attempt at the function. Are there more elegant ways to implement this?
const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' }
function findKey(obj, target) {
let result = null;
if (_.isEmpty(obj) || !_.isObject(obj)){
return null;
}
if (!_.isArray(obj) && Object.keys(obj).length > 0) {
for(let i=0; i < Object.keys(obj).length; i++){
let key = Object.keys(obj)[i];
let val = obj[key];
if (val === target) {
return key;
}else{
result = findKey(val, target);
}
if (result) {break}
}
}
return result;
}
console.log(findKey(foo, 'worked'))
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
For instance is there a way to avoid having to check the value of result to then break?
I feel like result should be able to bubble down the call stack until it returns at the very first function call without having to break.
This was recently brought back up, and one useful technique was not mentioned, generator functions. Often they simplify recursive traversals that need to stop early. Here we break the problem into two functions. One, the generator function nestedEntries gets all the (nested) key-value pairs in the object. The other calls that and returns the first one that matches a target value supplied.
function * nestedEntries (obj) {
for (let [k, v] of Object .entries (obj)) {
yield [k, v]
if (Object (v) === v) {yield * nestedEntries (v)}
}
}
const findKey = (obj, target) => {
for (let [k, v] of nestedEntries (obj)) {
if (v === target) return k
}
return null
}
const foo = {data01: 'rand01', data: {data21: 'rand', data2: { data3: 'worked' } }}
console .log (findKey (foo, 'worked'))
After the few questions made above, it looks like the function should:
Assume the input is always an object.
Assume it might encounter arrays in its way.
Assume it must stop after meeting one value (in case multiple value exists).
The provided input code given by the OP does not handle array cases.
Below code is sampled to work with these sample cases:
Plain nested object structure.
Object with nested arrays of objects or elements.
Below function accepts a second argument which is a callback to evaluate whether the element met is actually the one we're looking for. In this way, it's easier to handle more complex checks.
The recursive approach is kept and, once the key is met, the function simply return to avoid unnecessary searchs.
const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' };
const fooWithArrays = {
data: {
data2: {
data3: 'not here'
},
data4: [
{ data5: 'worked' },
{ data6: 'not me' }
]
}
};
const fooWithExpression = {
data: {
data2: {
data3: { id: 15, name: 'find me!' }
},
data21: {
data25: 'not me'
}
}
};
const findKeyByValue = (obj, equalsExpression) => {
// Loop key->value pairs of the input object.
for (var [key, v] of Object.entries(obj)) {
// if the value is an array..
if (Array.isArray(v)) {
// Loop the array.
for (let i = 0; i < v.length; i++) {
// check whether the recursive call returns a result for the nested element.
let res = findKeyByValue(v[i], equalsExpression);
// if so, the key was returned. Simply return.
if (res !== null && res !== undefined) return res;
}
}
// otherwise..
else {
// if the value is not null and not undefined.
if (v !== null && v !== undefined) {
// if the value is an object (typeof(null) would give object, hence the above if).
if (typeof(v) === 'object') {
// check whether the value searched is an object and the match is met.
if (equalsExpression(v)) return key;
// if not, recursively keep searching in the object.
let res = findKeyByValue(v, equalsExpression);
// if the key is found, return it.
if (res !== null && res !== undefined) return res;
}
else {
// finally, value must be a primitive or something similar. Compare.
let res = equalsExpression(v);
// if the condition is met, return the key.
if (res) return key;
// else.. continue.
}
}
else continue;
}
}
}
console.log( findKeyByValue(foo, (found) => found === 'worked') );
console.log( findKeyByValue(fooWithArrays, (found) => found === 'worked') );
console.log( findKeyByValue(fooWithExpression, (found) => found && found.id && found.id === 15) );
You can use Object.entries to iterate all the keys.
Also worth noting, Object.entries also works with Array's, so no
special handling required.
const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01', arr: [{arrtest: "arr"},'xyz']}
function findKey(obj, target) {
const fnd = obj => {
for (const [k, v] of Object.entries(obj)) {
if (v === target) return k;
if (typeof v === 'object') {
const f = fnd(v);
if (f) return f;
}
}
}
return fnd(obj);
}
console.log(findKey(foo, 'worked'))
console.log(findKey(foo, 'arr'))
console.log(findKey(foo, 'xyz'))
If obj is exactly a plain object with subobjects without arrays, this does the trick.
function findKey(obj, target) {
for (let key in obj) {
const val = obj[key];
if (val === target) {
return key;
}
if (typeof val === "object" && !Array.isArray(val)) {
const ret = findKey(val, target);
if (ret) return ret;
}
}
}
const foo = {
data: { data2: { data3: "worked" }, data21: "rand" },
data01: "rand01",
};
console.log(findKey(foo, "worked"));
console.log(findKey(foo, "bloop"));
You can try regex, if data is just objects without arrays:
const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' }
const out = JSON.stringify(foo).match(/"([^{}]+)":"worked"/)[1];
console.log(out);
For simple data processing tasks like this we use object-scan. It's very powerful once you wrap your head around it and makes things a lot cleaner. Here is how you'd solve your questions
(took the liberty to take the input data from #briosheje answer)
// const objectScan = require('object-scan');
const findKeyByValue = (data, fn) => objectScan(['**'], {
abort: true,
rtn: 'property',
filterFn: ({ value }) => fn(value) === true
})(data);
const foo = { data: { data2: { data3: 'worked' }, data21: 'rand' }, data01: 'rand01' };
const fooWithArrays = { data: { data2: { data3: 'not here' }, data4: [{ data5: 'worked' }, { data6: 'not me' }] } };
const fooWithExpression = { data: { data2: { data3: { id: 15, name: 'find me!' } }, data21: { data25: 'not me' } } };
console.log(findKeyByValue(foo, (found) => found === 'worked'));
// => data3
console.log(findKeyByValue(fooWithArrays, (found) => found === 'worked'));
// => data5
console.log(findKeyByValue(fooWithExpression, (found) => found && found.id && found.id === 15));
// => data3
console.log(findKeyByValue(fooWithExpression, (found) => false));
// => undefined
.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

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)});
}

Access to object property with dynamic name and dynamic nesting level

I read the property of an object I want to access from a string: level1.level2.property OR level1.property OR ... The names and the nesting may vary. I store the objects in a separate module (workerFunctions).
I know that I can access the objects dynamically with the []notation, e.g.:
var level1="level1";
var property="property";
console.log(workerFunctions[level1][property])
However, I don't know how to construct this "workerFunctions[level1][property]" dynamically from varying input strings, so to produce e.g.:
console.log(workerFunctions[level1][level2][property])
in consequence of the string: level1.level2.property.
Thank you in advance.
You could split the path and use the parts as properties for the given object.
function getValue(o, path) {
return path.split('.').reduce(function (o, k) {
return (o || {})[k];
}, o);
}
var o = { A : { B: { C: { value: 'Brenda' } } } };
console.log(getValue(o, 'A.B.C').value); // Brenda
console.log(getValue(o, 'Z.Y.X')); // undefined
For better use with dots in properties, you could use an array directly to avoid wrong splitting.
function getValue(o, path) {
return path.reduce(function (o, k) {
return (o || {})[k];
}, o);
}
var o = { A : { 'B.C': { value: 'Brenda' } } };
console.log(getValue(o, ['A', 'B.C', 'value'])); // Brenda
console.log(getValue(o, ['Z.Y.X'])); // undefined
This should do it :
const str = 'level1.level2.property';
let value = { //workerFunctions;
level1: {
level2: {
property: 'this is the value'
}
}
};
str.split(/\./).forEach((prop) => {
value = value[prop];
});
console.log(value);

Convert javascript object camelCase keys to underscore_case

I want to be able to pass any javascript object containing camelCase keys through a method and return an object with underscore_case keys, mapped to the same values.
So, I have this:
var camelCased = {firstName: 'Jon', lastName: 'Smith'}
And I want a method to output this:
{first_name: 'Jon', last_name: 'Jon'}
What's the fastest way to write a method that takes any object with any number of key/value pairs and outputs the underscore_cased version of that object?
Here's your function to convert camelCase to underscored text (see the jsfiddle):
function camelToUnderscore(key) {
return key.replace( /([A-Z])/g, "_$1").toLowerCase();
}
console.log(camelToUnderscore('helloWorldWhatsUp'));
Then you can just loop (see the other jsfiddle):
var original = {
whatsUp: 'you',
myName: 'is Bob'
},
newObject = {};
function camelToUnderscore(key) {
return key.replace( /([A-Z])/g, "_$1" ).toLowerCase();
}
for(var camel in original) {
newObject[camelToUnderscore(camel)] = original[camel];
}
console.log(newObject);
If you have an object with children objects, you can use recursion and change all properties:
function camelCaseKeysToUnderscore(obj){
if (typeof(obj) != "object") return obj;
for(var oldName in obj){
// Camel to underscore
newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
// Only process if names are different
if (newName != oldName) {
// Check for the old property name to avoid a ReferenceError in strict mode.
if (obj.hasOwnProperty(oldName)) {
obj[newName] = obj[oldName];
delete obj[oldName];
}
}
// Recursion
if (typeof(obj[newName]) == "object") {
obj[newName] = camelCaseKeysToUnderscore(obj[newName]);
}
}
return obj;
}
So, with an object like this:
var obj = {
userId: 20,
userName: "John",
subItem: {
paramOne: "test",
paramTwo: false
}
}
newobj = camelCaseKeysToUnderscore(obj);
You'll get:
{
user_id: 20,
user_name: "John",
sub_item: {
param_one: "test",
param_two: false
}
}
es6 node solution below. to use, require this file, then pass object you want converted into the function and it will return the camelcased / snakecased copy of the object.
const snakecase = require('lodash.snakecase');
const traverseObj = (obj) => {
const traverseArr = (arr) => {
arr.forEach((v) => {
if (v) {
if (v.constructor === Object) {
traverseObj(v);
} else if (v.constructor === Array) {
traverseArr(v);
}
}
});
};
Object.keys(obj).forEach((k) => {
if (obj[k]) {
if (obj[k].constructor === Object) {
traverseObj(obj[k]);
} else if (obj[k].constructor === Array) {
traverseArr(obj[k]);
}
}
const sck = snakecase(k);
if (sck !== k) {
obj[sck] = obj[k];
delete obj[k];
}
});
};
module.exports = (o) => {
if (!o || o.constructor !== Object) return o;
const obj = Object.assign({}, o);
traverseObj(obj);
return obj;
};
Came across this exact problem when working between JS & python/ruby objects. I noticed the accepted solution is using for in which will throw eslint error messages at you ref: https://github.com/airbnb/javascript/issues/851 which alludes to rule 11.1 re: use of pure functions rather than side effects ref:https://github.com/airbnb/javascript#iterators--nope
To that end, figured i'd share the below which passed the said rules.
import { snakeCase } from 'lodash'; // or use the regex in the accepted answer
camelCase = obj => {
const camelCaseObj = {};
for (const key of Object.keys(obj)){
if (Object.prototype.hasOwnProperty.call(obj, key)) {
camelCaseObj[snakeCase(key)] = obj[key];
}
}
return camelCaseObj;
};
Marcos Dimitrio posted above with his conversion function, which works but is not a pure function as it changes the original object passed in, which may be an undesireable side effect. Below returns a new object that doesn't modify the original.
export function camelCaseKeysToSnake(obj){
if (typeof(obj) != "object") return obj;
let newObj = {...obj}
for(var oldName in newObj){
// Camel to underscore
let newName = oldName.replace(/([A-Z])/g, function($1){return "_"+$1.toLowerCase();});
// Only process if names are different
if (newName != oldName) {
// Check for the old property name to avoid a ReferenceError in strict mode.
if (newObj.hasOwnProperty(oldName)) {
newObj[newName] = newObj[oldName];
delete newObj[oldName];
}
}
// Recursion
if (typeof(newObj[newName]) == "object") {
newObj[newName] = camelCaseKeysToSnake(newObj[newName]);
}
}
return newObj;
}
this library does exactly that: case-converter
It converts snake_case to camelCase and vice versa
const caseConverter = require('case-converter')
const snakeCase = {
an_object: {
nested_string: 'nested content',
nested_array: [{ an_object: 'something' }]
},
an_array: [
{ zero_index: 0 },
{ one_index: 1 }
]
}
const camelCase = caseConverter.toCamelCase(snakeCase);
console.log(camelCase)
/*
{
anObject: {
nestedString: 'nested content',
nestedArray: [{ anObject: 'something' }]
},
anArray: [
{ zeroIndex: 0 },
{ oneIndex: 1 }
]
}
*/
following what's suggested above, case-converter library is deprectaed, use snakecase-keys instead -
https://github.com/bendrucker/snakecase-keys
supports also nested objects & exclusions.
Any of the above snakeCase functions can be used in a reduce function as well:
const snakeCase = [lodash / case-converter / homebrew]
const snakeCasedObject = Object.keys(obj).reduce((result, key) => ({
...result,
[snakeCase(key)]: obj[key],
}), {})
jsfiddle
//This function will rename one property to another in place
Object.prototype.renameProperty = function (oldName, newName) {
// Do nothing if the names are the same
if (oldName == newName) {
return this;
}
// Check for the old property name to avoid a ReferenceError in strict mode.
if (this.hasOwnProperty(oldName)) {
this[newName] = this[oldName];
delete this[oldName];
}
return this;
};
//rename this to something like camelCase to snakeCase
function doStuff(object) {
for (var property in object) {
if (object.hasOwnProperty(property)) {
var r = property.replace(/([A-Z])/, function(v) { return '_' + v.toLowerCase(); });
console.log(object);
object.renameProperty(property, r);
console.log(object);
}
}
}
//example object
var camelCased = {firstName: 'Jon', lastName: 'Smith'};
doStuff(camelCased);
Note: remember to remove any and all console.logs as they aren't needed for production code

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