How to check if and object is empty (deep)? - javascript

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

Related

Recursive javascript function that converts nested object keys to string and store all keys in arrray

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

Replace Symbol Keys in Object With Text

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.

How to check if a value in object is a primitive?

So basically i want to check if my data (which is in JSON Format) has a value which is a primitive. So let's take an Example: I get data that looks like this: {name: Artikel, value: {"ArtNr": 1234}} and i want to check if 1234 is primitive or not. I also want to differentiate if the result is an Array with Primitives in it or an Object. Is that possible?
function isObjectContainingPrimitiveValues(test) {
let values = Object.values(test);
for (let i of values) {
console.log(i);
return (typeof i === 'string' || typeof i === 'number' || typeof i === 'boolean' || typeof i === null || typeof i === undefined);
}
}
UPDATE
So with the awesome help of MaxK i have built a isResultContainingPrimitiveValues() Function which checks my data for Primitive/ Primitive Arrays and or Objects. The following part is the trickiest at least with my understanding. The following Example will hopefully help you understand my problems better.
So my let namesSplit = treeNode.name.split('.'); variable splits the data it gets and has as a result of nameSplit : Artikel,Artnr. Next i defined a key variable let key = namesSplit[0]; which has key : Artikel as a result. Than i define a contextEntry variable let contextEntry = exprData.contextEntry.find(_x => _x.name === key); and has contextEntry : {"name":"Artikel","value":{"ArtNr":123}} as a result. Now i want to check: if there's another split namesSplit.length > 1 check isResultContainingPrimitiveValues(). If it is primitive, throw an error, if it is an object -> get values from it and if it is an array -> get values form there. I know it's a lot but from all the confusing stuff i can't seem to think clear, so i appreciate every help i can get.
You are returning from your function on the first iteration. You should only return false if you found an non-primitive and if you were able to loop over all values you can return true because all values are primitives:
function isObjectContainingPrimitiveValues(testObj) {
let values = Object.values(testObj);
for(let i of values){
if (typeof i === 'object') {
return false;
}
}
return true;
};
Update:
After reading your comment i changed the code to check for arrays with primitives as well. The idea is, to create a new function which only checks if a single value is a primitive.Now if we find an array, we can simply check - with the help
of the arrays some function - if some element, inside the array is not primitive. If so return false,otherwise we do the same checks as before:
function isObjectContainingPrimitiveValues(testObj) {
let values = Object.values(testObj);
for (let i of values) {
if (Array.isArray(i)) {
if (i.some(val => !isPrimitive(val)))
return false;
} else {
if (!isPrimitive(i))
return false;
}
}
return true;
};
function isPrimitive(test) {
return typeof test !== 'object'
}
Array and object types all return a 'typeof' 'object'. so you can check against an object instead of checking against multiple conditions.
So the return statement will be:
return (typeof i === 'object').
Number, string, undefined, null will all return false on the statement above.

How to delete empty instances from an array while using a recursive clean function?

To give some background: By using Postman (the REST api tool) we are comparing XMLs to a template by converting the XMLs to JSON and compare those as Javascript objects. The comparison can handle wildcards in the values and will return a new JS object (or JSON) with only the differences. When there are no differences, I receive an empty object which is the correct state. In some cases empty values or objects are returned and we remove them from the object with a clean step.
This is how the clean function looks like:
Utils = {
clean: function(object) {
Object
.entries(object)
.forEach(([k, v]) => {
if (v && typeof v === 'object')
Utils.clean(v);
if (v && typeof v === 'object' && !Object.keys(v).length || v === null || v === undefined)
Array.isArray(object) ? object.splice(k, 1) : delete object[k];
});
return object;
}
}
This works fine for most cases except when we have an array with multiple the same empty object because of the object.splice in combination with the foreach as pointed out here.
Normally, I would use a filter function, use _.pickBy from lodash or iterate backwards through the array, but because of the layout of the clean function, I can not figure out how to do that.
Can you help me to point out what I need to do to remove multiple empty items and objects from an array correctly.
Real life testcase:
var x = {"Document":{"CstmrDrctDbtInitn":{"GrpHdr":{},"PmtInf":{"DrctDbtTxInf":[{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}},{"PmtId":{}}]}}}};
console.log(JSON.stringify(Utils.clean(x)));
// returns {"Document":{"CstmrDrctDbtInitn":{"PmtInf":{"DrctDbtTxInf":[{},{}]}}}}
// desired result: {}
Other testcases:
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// returns {"a":[null,"c",{},{},{}]}
// desired: {"a":["b", "c"]}
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// returns {"a":[null,"c",{},{}]}
// desired: {"a":["b", "c"]}
console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// returns {"a":[null,{}]}
// desired: {}
Give this a shot, and here's a working example: https://jsfiddle.net/3rno4L7d/
Utils Object (with extra helpers)
const Utils = {
doDelete: function(val) {
return !Boolean(val) ||
Utils.isEmptyObj(val) ||
Utils.isEmptyArray(val);
},
isEmptyArray: function(val) {
return Array.isArray(val) && val.length === 0;
},
isEmptyObj: function(obj) {
return Object.keys(obj).length === 0 &&
obj.constructor === Object;
},
hasKeys: function(obj) {
return Object.keys(obj).length > 0;
},
clean: function(object) {
Object
.keys(object)
.forEach(key => {
const val = object[key];
// If dealing with an object, clean it.
if (val && typeof val === 'object') {
Utils.clean(val);
}
// If deleteable, delete and return
if (Utils.doDelete(val)) {
delete object[key];
return object;
}
// If array, loop over entries
if (Array.isArray(val)) {
let i = val.length;
// While lets us delete from the array without affecting the loop.
while (i--) {
let entry = val[i];
// If deleteable, delete from the array
if (Utils.doDelete(entry)) {
val.splice(i, 1)
} else if (Utils.hasKeys(entry)) {
// If an object, clean it
entry = Utils.clean(entry);
// Check to see if cleaned object is deleteable
if (Utils.doDelete(entry)) {
val.splice(i, 1)
}
}
}
// Once done with the array, check if deleteable
if (Utils.doDelete(val)) {
delete object[key];
}
}
});
return object;
}
}
Output
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{},{},{},{}]})));
// Returns {"a":["b","c"]}
console.log(JSON.stringify(Utils.clean({"a": [null,null,"b","c",{"d": {}},{}]})));
// Returns {"a":["b","c"]}
console.log(JSON.stringify(Utils.clean({ "a" : [null,null,{"d": {}, "e": [null, {}]},{}]})));
// Returns {}

Javascript object recursion to find an item at the deepest level

I'm trying to recursively search an object that contains strings, arrays, and other objects to find an item (match a value) at the deepest level however I'm always getting undefined as the return result. I can see through some console logging that I am finding the item but it gets overwritten. Any idea where I'm going wrong?
var theCobWeb = {
biggestWeb: {
item: "comb",
biggerWeb: {
items: ["glasses", "paperclip", "bubblegum"],
smallerWeb: {
item: "toothbrush",
tinyWeb: {
items: ["toenails", "lint", "wrapper", "homework"]
}
}
},
otherBigWeb: {
item: "headphones"
}
}
};
function findItem (item, obj) {
var foundItem;
for (var key in obj) {
if (obj[key] === item) {
foundItem = obj;
} else if (Array.isArray(obj[key]) && obj[key].includes(item)) {
foundItem = obj;
} else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
findItem(item, obj[key]);
}
}
return foundItem;
}
var foundIt = findItem('glasses', theCobWeb);
console.log('The item is here: ' + foundIt); // The item is here: undefined
Edit: cleaned up the code a bit based on feedback below.
function findItem (item, obj) {
for (var key in obj) {
if (obj[key] === item) { // if the item is a property of the object
return obj; // return the object and stop further searching
} else if (Array.isArray(obj[key]) && obj[key].includes(item)) { // if the item is inside an array property of the object
return obj; // return the object and stop the search
} else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { // if the property is another object
var res = findItem(item, obj[key]); // get the result of the search in that sub object
if(res) return res; // return the result if the search was successful, otherwise don't return and move on to the next property
}
}
return null; // return null or any default value you want if the search is unsuccessful (must be falsy to work)
}
Note 1: Array.isArray and Array.prototype.includes already returning booleans so there is no need to check them against booleans.
Note 2: You can flip the value of a boolean using the NOT operator (!).
Note3: You have to return the result (if found) immediately after it is found so you won't waste time looking for something you already have.
Note4: The return result of the search will be an object (if found) and since objects are passed by reference not by value, changing the properties of that object will change the properties of the original object too.
Edit: Find the deepest object:
If you want to find the deepest object, you'll have to go throug every object and sub-object in the object obj and everytime you have to store the object and it's depth (if the depth of the result is bigger than the previous result of course). Here is the code with some comments (I used an internal function _find that actually get called on all the objects):
function findItem (item, obj) {
var found = null; // the result (initialized to the default return value null)
var depth = -1; // the depth of the current found element (initialized to -1 so any found element could beat this one) (matched elements will not be assigned to found unless they are deeper than this depth)
function _find(obj, d) { // a function that take an object (obj) and on which depth it is (d)
for (var key in obj) { // for each ...
// first call _find on sub-objects (pass a depth of d + 1 as we are going to a one deep bellow)
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
_find(obj[key], d + 1);
}
// then check if this object actually contain the item (we still at the depth d)
else if (obj[key] === item || (Array.isArray(obj[key]) && obj[key].includes(item))) {
// if we found something and the depth of this object is deeper than the previously found element
if(d > depth) {
depth = d; // then assign the new depth
found = obj; // and assign the new result
}
}
}
}
_find(obj, 0); // start the party by calling _find on the object obj passed to findItem with a depth of 0
// at this point found is either the initial value (null) means nothing is found or it is an object (the deepest one)
return found;
}
"I'm trying to recursively search an object that contains strings, arrays, and other objects to find an item (match a value) at the deepest level however I'm always getting undefined as the return result."
var foundIt = findItem('glasses', theCobWeb);
console.log('The item is here: ' + foundIt); // The item is here: undefined
"The item is here ..." – where?
Well what exactly do you want as a return value? Should it just say "glasses" when it's all done? In my opinion, that's kind of pointless – fundamentally it's no better than just returning true or false.
I wrote this function a while ago because I needed to search a heap of data but also know specifically where it matched. I'd probably revise this a little bit now (or at least include type annotations), but it works as-is, so here you go.
// helpers
const keys = Object.keys
const isObject = x=> Object(x) === x
const isArray = Array.isArray
const rest = ([x,...xs])=> xs
// findDeep
const findDeep = (f,x) => {
let make = (x,ks)=> ({node: x, keys: ks || keys(x)})
let processNode = (parents, path, {node, keys:[k,...ks]})=> {
if (k === undefined)
return loop(parents, rest(path))
else if (isArray(node[k]) || isObject(node[k]))
return loop([make(node[k]), make(node, ks), ...parents], [k, ...path])
else if (f(node[k], k))
return {parents, path: [k,...path], node}
else
return loop([{node, keys: ks}, ...parents], path)
}
let loop = ([node,...parents], path) => {
if (node === undefined)
return {parents: [], path: [], node: undefined}
else
return processNode(parents, path, node)
}
return loop([make(x)], [])
}
// your sample data
var theCobWeb = {biggestWeb: {item: "comb",biggerWeb: {items: ["glasses", "paperclip", "bubblegum"],smallerWeb: {item: "toothbrush",tinyWeb: {items: ["toenails", "lint", "wrapper", "homework"]}}},otherBigWeb: {item: "headphones"}}};
// find path returns {parents, path, node}
let {path, node} = findDeep((value,key)=> value === "glasses", theCobWeb)
// path to get to the item, note it is in reverse order
console.log(path) // => [0, 'items', 'biggerWeb', 'biggestWeb']
// entire matched node
console.log(node) // => ['glasses', 'paperclip', 'bubblegum']
The basic intuition here is node[path[0]] === searchTerm
Complete path to the matched query
We get the entire key path to the matched data. This is useful because we know exactly where it is based on the root of our search. To verify the path is correct, see this example
const lookup = ([p,...path], x) =>
(p === undefined) ? x : lookup(path,x)[p]
lookup([0, 'items', 'biggerWeb', 'biggestWeb'], theCobWeb) // => 'glasses'
Unmatched query
Note if we search for something that is not found, node will be undefined
let {path, node} = findDeep((value,key)=> value === "sonic the hog", theCobWeb)
console.log(path) // => []
console.log(node) // => undefined
Searching for a specific key/value pair
The search function receives a value and key argument. Use them as you wish
let {path, node} = findDeep((value,key)=> key === 'item' && value === 'toothbrush', theCobWeb)
console.log(path) // => [ 'item', 'smallerWeb', 'biggerWeb', 'biggestWeb' ]
console.log(node) // => { item: 'toothbrush', tinyWeb: { items: [ 'toenails', 'lint', 'wrapper', 'homework' ] } }
Short circuit – 150cc
Oh and because I spoil you, findDeep will give an early return as soon as the first match is found. It won't waste computation cycles and continue iterating through your pile of data after it knows the answer. This is a good thing.
Go exploring
Have courage, be adventurous. The findDeep function above also gives a parents property on returned object. It's probably useful to you in some ways, but it's a little more complicated to explain and not really critical for answering the question. For the sake of keeping this answer simplified, I'll just mention it's there.
That's because the recursive call doesn't assign the return to the variable.
And you should check the return from the recursive call and return if true or break from the for loop if you have other logic after it.
function findItem(item, obj) {
for (var key in obj) {
if (obj[key] === item) {
return obj;
} else if (Array.isArray(obj[key]) === true && obj[key].includes(item) === true) {
return obj;
} else if (typeof obj[key] === 'object' && Array.isArray(obj[key]) === false) {
var foundItem = findItem(item, obj[key]);
if(foundItem)
return foundItem;
}
}

Categories