I have the following object like this:
{ where: { [Symbol(or)]: [ [Object], [Object] ] },hooks: true, rejectOnEmpty: false }
I'm calling JSON.stringify on this, and it gets converted to:
{"where":{},"hooks":true,"rejectOnEmpty":false}
I think this is because [Symbol(or)] evaluates to undefined so stringify removes it.
This value is coming from Sequelize operators, specifically Op.or. Is there a way stringify can convert this to a String so I would instead receive:
{"where":{"[Symbol(or)]": [[<<stringifiedObject>>], [<<stringifiedObject>>]]},"hooks":true,"rejectOnEmpty":false}
I know I could pass a function to JSON.stringify which would replace undefined with something, but I would like to maintain the original Symbol in the string replacement, so that I can distinguish between Symbol(and) and Symbol(or), even though both would evaluate to undefined.
Solved with the following function:
const cleanObject = (obj) => {
try {
const keys = Reflect.ownKeys(obj);
const ret = {};
keys.forEach((key) => {
let val = obj[key];
if (Object.prototype.toString.call(val) === '[object Object]') {
val = cleanObject(val);
}
if (typeof key === 'symbol') {
const newKey = `${String(key)}`;
ret[newKey] = val;
} else {
ret[key] = val;
}
});
return ret;
} catch (ex) {
console.log(ex);
}
};
Reflect.ownKeys returns all keys, including Symbols.
Next, I check if a given key is a Symbol and replace it with a string. I also call the function recursively to apply the same logic to all nested objects.
Related
I am trying to write a javascript recursive function that receives one parameter - nested JSON object.
The function goes through the potentially infinitely nested object and converts all the keys (property names) to a string that is stored in array. Array is returned to a place where the function was called.
Example of JSON object:
{
OBJECT1: {
ATTRIBUTE3: {
PARAMETER2: {
PROPERTY1: {
}
}
}
}
}
The object does not hold any values.
What i tried and did not work:
function convertKeysToString(obj) {
let keys = [];
for (let key in obj) {
if (typeof obj[key] === 'object') {
keys = keys.concat(convertKeysToString(obj[key]));
} else {
keys.push(key.toString());
}
}
return keys;
}
As a result, I expected that returned key is pushed to an array, but the funciton didnt get the key at all or was not pushed to keys array.
Another code I tried:
function getNestedObjectKeys(obj) {
var keys = []
var firstLevel = null
var property = Object.keys(obj)
property = property[0]
firstLevel = Object.keys(obj[property])[0]
if (firstLevel == undefined) {
return 0
}
let returnedValue = keys.unshift(getNestedObjectKeys(obj[property]))
if (returnedValue == 0) {
return Object.keys(obj[property])[0]
}
returnedValue = Object.keys(obj[property])[0]
if (returnedValue != obj[property[0]]) {
return Object.keys(obj[property])[0]
}
else if (returnedValue == firstLevel) {
return keys
}
}
The function should return the key name and push (unshift) it to string and then return it, but the unshift doesnt do what I expect and in the returnedValue is not a expected returned string.
I approached it the way that the function findd the deepest (empty) object, and starts returning the name of the key. The thing is that I must return the key name AND push it to the string, which I can't find the way to accomplish at once.
Your first solution is pretty close, but has one problem (well, one main problem): when the value is type object, you don't add its key to the array. So how is it supposed to get into the array? Give this a shot:
function convertKeysToString(obj) {
let keys = [];
for (let key in obj) {
keys.push(key.toString());
if (typeof obj[key] === 'object') {
keys = keys.concat(convertKeysToString(obj[key]));
}
}
return keys;
}
Other things you may want to consider:
typeof null is object.
typeof [] is also object.
You could have a look to object, which are truthy and typeof object.
const
getKeys = object => (keys => [
...keys.flatMap(key => object[key] && typeof object[key] === 'object'
? [key, ...getKeys(object[key])]
: [key]
)
])(Object.keys(object)),
data = { OBJECT1: { ATTRIBUTE3: { PARAMETER2: { PROPERTY1: {} } } } },
result = getKeys(data);
console.log(result);
Take this:
var lists:{
item1:{}
,item2:{}
,item3:{}
,item4:{}
}
Since it's substantially empty, I want a function (maybe but not necessarily a _lodash one) that checks it and say that is empty.
Something like
is_empty(lists) // >> true (because every property resolves to an empty object)
How to?
You can iterate over the values of the object and check if all of them are empty:
var lists = {
item1:{},
item2:{},
item3:{},
item4:{}
}
//ES6:
function isEmpty(obj) {
return Object.keys(obj).every(k => !Object.keys(obj[k]).length)
}
console.log(isEmpty(lists));
// ES5
function isEmpty(obj) {
return Object.keys(obj).every(function(k) {
return !Object.keys(obj[k]).length}
)
}
console.log(isEmpty(lists));
If lists is always an object of objects, you can iterate over all values with Object.values and check that each value (inner object) has no keys:
const isEmpty = outer => Object.values(outer).every(
inner => Object.keys(inner).length === 0
);
var lists = {
item1:{}
,item2:{}
,item3:{}
,item4:{}
}
var lists2 = {
item1:{}
,item2:{}
,item3:{}
,item4:{}
,item5:{ foo: 'bar' }
}
console.log(isEmpty(lists));
console.log(isEmpty(lists2));
This solution with check for the emptyness of the eternally nested object.
Note: This will treat empty string '' and boolean false as empty as well. If you need special support for stings then may be you can do some tweaking in the below code.
const isDeeplyEmpty = item => {
if(typeof item === 'boolean') return !item;
else if(typeof item === 'number') return false;
else if(typeof item === 'object') {
return Object.keys(item).every(k => {
if(['object', 'boolean', 'number'].includes(typeof item[k])) {
return isDeeplyEmpty(item[k]);
}
return _.isEmpty(item[k]);
})
}
return !item;
};
I need to set all properties of some object to null.
But the object can be very big, so I can't just do it one by one.
How to set all properties at once?
Here's a useful function called 'Object.keys()', it returns all of the attribute names of an object.
let setAll = (obj, val) => Object.keys(obj).forEach(k => obj[k] = val);
let setNull = obj => setAll(obj, null);
Non-arrow-function version:
function setAll(obj, val) {
/* Duplicated with #Maksim Kalmykov
for(index in obj) if(obj.hasOwnProperty(index))
obj[index] = val;
*/
Object.keys(obj).forEach(function(index) {
obj[index] = val
});
}
function setNull(obj) {
setAll(obj, null);
}
If you are looking for a short one-liner to copy and paste, use this
Object.keys(obj).forEach((i) => obj[i] = null);
Another way of doing it, using Array.reduce. It does not overwriting the existing object. This only works if the object only have simple values.
const newObj = Object.keys(originalObj).reduce(
(accumulator, current) => {
accumulator[current] = null;
return accumulator
}, {});
You can use Object.keys() as Nianyi Wang mentioned in his answer, or a for in, like this:
for (key in obj) {
if (obj.hasOwnProperty(key)) {
obj[key] = null;
}
}
But in this case you should check hasOwnProperty().
But the object can be very big, so I can't just do it one by one.
By "big" do you mean "millions of properties" and you are concerned about performance? Or do you mean "a bunch of properties you don't know the names of, and/or don't want to have list out"?
How to set all properties at once?
You can't. One way or another, you have to loop.
Instead of mutating an existing object, consider creating a new, empty object. Its property values will be undefined, but that could work depending on your code structure.
Lodash can manage this using cloneDeepWith.
My solution to the same problem:
import * as _ from 'lodash';
const bigObj = {"big": true, "deep": {"nested": {"levels": "many" } } };
const blankObj = _.cloneDeepWith(bigObj, (value) => {return _.isObject(value) ? undefined : null});
console.log(blankObj);
// outputs { big: null, deep: { nested: { levels: null } } }
Returning undefined in the customizer was not obvious to me, but this answer explains that doing so triggers recursion.
If object contains child object, if you want to set all child object properties to null, recursive solution is below.
function setEmpty(input){
let keys = Object.keys(input);
for( let key of keys ){
if(typeof input[key] != "object" ){
input[key] = null;
}else{
setEmpty(input[key]);
}
}
return input;
}
you can use for in. Here is an example:
let obj = {prob1:"value1", prob2:"value2"}
for(let prob in obj){obj[prob]=null}
export const setObjToNull = (obj) => {
var returnObj = {};
Object.keys(obj).map((key) => {
let nullObj = { [key]: '' };
Object.assign(returnObj, nullObj);
})
return returnObj;
}
You can use Object.fromEntries & Object.entries like this
Object.fromEntries(
Object.keys(obj).map((key) => [key, null])
)
let values = {
a:1,
b:'',
c: {
a:'',
s:4,
d: {
q: '',
w: 8,
e: 9
}
}
}
values;
const completeWithNull = (current) => {
Object.keys(current).forEach((key) => {
current[key] = current[key] === ''? null
: typeof current[key] === 'object' ? completeWithNull(current[key])
: current[key]
});
return current;
};
completeWithNull(values);
I am creating a dynamic javascript object to query from the mongodb, and my code as follows,
_.each(separatedFilter, function (str) {
const filter = str.split('=');
console.log('filter is', JSON.stringify(filter));
if (filter[1] && filter[1].trim() !== '') {
var key = `${filter[0]}`;
var obj = {};
obj[key] = filter[1];
if (key = 'date.start:{$gte') {
key = '"date.start":{"$gte"';
}
if (key = 'date.end:{$lt') {
key = '"date.end":{"$lt"';
}
query.push(obj);
}
});
the above code creates a object as follows,
{ '$and':
[ { name: [Object] },
{},
{ '"date.start":{"$gte"': '2016-12-18T18:30:00.000Z',
'"date.end":{"$lt"': '2016-12-18T18:30:00.000Z' }
] }
in the above object i could see ' at the start and end of date.start and date.end. whereas i just want it to be,
{ '$and':
[ { name: [Object] },
{},
{ "date.start":{"$gte": '2016-12-18T18:30:00.000Z',
"date.end":{"$lt": '2016-12-18T18:30:00.000Z' }
] }
You cannot define a value (the nested object) via a key, nor can you partially define an object with a key (i.e. $gte) but without a value.
A fix would look something like this:
if (key === 'date.start:{$gte') {
obj['date.start'] = { '$gte' : null };
}
if (key === 'date.end:{$lt') {
obj['date.start'] = { '$lt' : null };
}
obj[key] = filter[1];
This assumes that null will be replaced with the appropriate timestamps somewhere else in the program and then you can serialize that object to a string before sending the query.
Explanation
These lines have a number of problems:
if (key = 'date.start:{$gte') {
key = '"date.start":{"$gte"';
}
if (key = 'date.end:{$lt') {
key = '"date.end":{"$lt"';
}
They are inserting quotes into the key for some reason. Probably just a mistake.
They are assigning to key within the conditional statement, rather than doing a comparison. This will have the effect of always executing the code within the if block, because the string being assigned and returned is always truthy.
They come after the key and its value are already assigned to the object via obj[key] = filter[1]. Since the assignment has already run by the time the if statements do, they have no chance to affect the key that is used in the assignment.
Also, this line:
var key = `${filter[0]}`;
Can be simplified, as it is the same as:
var key = filter[0];
Or even better with let and destructuring:
let [key] = filter;
I have an array of objects, I would like to remove 'undefined' from any of the properties in any of the objects.
To remove undefined from an object, I use this method,
removeNullorUndefined:function(model) {
function recursiveFix(o) {
// loop through each property in the provided value
for (var k in o) {
// make sure the value owns the key
if (o.hasOwnProperty(k)) {
if (o[k] === 'undefined') {
// if the value is undefined, set it to 'null'
o[k] = '';
} else if (typeof (o[k]) !== 'string' && o[k].length > 0) {
// if there are sub-keys, make a recursive call
recursiveFix(o[k]);
}
}
}
}
var cloned = $.extend(true, {}, model);
recursiveFix(cloned);
return cloned;
},
How can I modify this so it can also accept an array of objects and remove 'undefined' from it ?
Appreciate any inputs
As long as the value is undefined and not a string value of 'undefined' then one way is to use JSON.stringify. Referring to property values:
If undefined, a function, or a symbol is encountered during conversion it is either omitted (when it is found in an object) or censored to null (when it is found in an array). JSON.stringify can also just return undefined when passing in "pure" values like JSON.stringify(function(){}) or JSON.stringify(undefined).
So, you could stringify an object and immediately parse it to remove undefined values.
NOTE: This approach will deep clone the entire object. In other words if references need to be maintained this approach won't work.
var obj = {
foo: undefined,
bar: ''
};
var cleanObj = JSON.parse(JSON.stringify(obj));
// For dispaly purposes only
document.write(JSON.stringify(cleanObj, null, 2));
An added bonus is without any special logic it will work at any depth:
var obj = {
foo: {
far: true,
boo: undefined
},
bar: ''
};
var cleanObj = JSON.parse(JSON.stringify(obj));
// For dispaly purposes only
document.write(JSON.stringify(cleanObj, null, 2));
If it is a string value of 'undefined' you can use the same approach but with a replacer function:
var obj = {
foo: {
far: true,
boo: 'undefined'
},
bar: ''
};
var cleanObj = JSON.parse(JSON.stringify(obj, replacer));
function replacer(key, value) {
if (typeof value === 'string' && value === 'undefined') {
return undefined;
}
return value;
}
// For dispaly purposes only
document.write(JSON.stringify(cleanObj, null, 2));
If you like the way removeNullorUndefined() currently works then you might try:
items.forEach(function(item){ removeNullorUndefined(item); });