I have a following object:
{
a: {
b: {
c: undefined
}
},
b: {
c: 15,
d: []
},
c: {
d: [11, undefined ,12],
e: {}
}
}
And i need to get this:
{
b: {
c: 15
},
c: {
d: [11, 12]
}
}
I found this function (source: Remove undefined properties from object )
function filter(obj) {
for (var key in obj) {
if (obj[key] === undefined) {
delete obj[key];
continue;
}
if (obj[key] && typeof obj[key] === "object") {
filter(obj[key]);
if (!Object.keys(obj[key]).length) {
delete obj[key];
}
}
}
return obj;
}
But it just delete elements of array and it turns out the following
{
b: {
c: 15
},
c: {
d: [11, empty ,12]
}
}
You need a recursive solution. Make a function that takes a value and returns something falsey if its value is falsey, or if all of its recursive elements are falsey or empty arrays or without keys:
const removeRecursive = (obj) => {
// Falsey primitive, including null:
if (!obj) return;
// Truthy primitive (or function):
if (typeof obj !== 'object') return obj;
// Array, transform all values and return the new array
// if there are any truthy transformed values:
if (Array.isArray(obj)) {
const newArr = obj.map(removeRecursive).filter(Boolean);
return newArr.length ? newArr : undefined;
}
// Otherwise, it's an object:
const newObj = Object.fromEntries(
Object.entries(obj)
.map(([key, val]) => ([key, removeRecursive(val)]))
.filter(([, val]) => val)
);
if (Object.keys(newObj).length) {
return newObj;
}
};
const obj = {
a: {
b: {
c: undefined
}
},
b: {
c: 15,
d: []
},
c: {
d: [11, undefined ,12],
e: {}
}
};
console.log(removeRecursive(obj));
Related
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
},
},
};
const obj2 = {
a: 5,
e: {
c: 10,
},
};
need to get ['l', 'b'] or maybe not in the array
Here's a recursive function that deep compares the keys of both objects. This also takes into account the structure and nesting of the children.
So essentially, it goes through each nested key in obj1 and makes sure that there's an equivalent key in the same location in obj2
Your example
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
},
},
};
const obj2 = {
a: 5,
e: {
c: 10,
},
};
const missingKeys = []
function compare(obj1, obj2) {
for (let prop in obj1) {
if (obj2[prop]) {
if (typeof obj1[prop] === 'object' && typeof obj2[prop] === 'object') {
compare(obj1[prop], obj2[prop])
}
} else {
if (typeof obj1[prop] === 'object') {
compare(obj1[prop], {})
}
missingKeys.push(prop)
}
}
}
compare(obj1, obj2)
console.log(missingKeys)
Example 2:
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
d: 20,
},
},
z: 50
};
const obj2 = {
a: 5,
e: {
c: 10,
},
b: 50, // shares same key name but nested in different location
l: 50, // also shares same key but nested differently
z: 50,
};
const missingKeys = []
function compare(obj1, obj2) {
for (let prop in obj1) {
if (obj2[prop]) {
if (typeof obj1[prop] === 'object' && typeof obj2[prop] === 'object') {
compare(obj1[prop], obj2[prop])
}
} else {
if (typeof obj1[prop] === 'object') {
compare(obj1[prop], {})
}
missingKeys.push(prop)
}
}
}
compare(obj1, obj2)
console.log(missingKeys)
This would work. Without checking the levels and assuming the unique field names.
const obj1 = {
a: 5,
e: {
c: 10,
l: {
b: 50,
},
},
};
const obj2 = {
a: 5,
e: {
c: 10,
},
};
const getAllKeys = (obj) => {
let keyNames = Object.keys(obj);
Object.values(obj).forEach((value) => {
if (typeof value === "object") {
keyNames = keyNames.concat(getAllKeys(value));
}
});
return keyNames;
};
const getFilteredKeys = (keySet1, keySet2) =>
keySet1.filter((key) => !keySet2.includes(key));
const output = getFilteredKeys(getAllKeys(obj1), getAllKeys(obj2));
console.log(output);
const isFieldEmpty = (data) => {
const newObj = Object.keys(data).map((key) => {
if (data[key] === '') {
const tempKey = key;
key.value = 'N/A';
return tempKey;
}
return key;
});
return newObj;
};
It should search for all empty strings in the object then replace with N/A. Above is my attempt, but it's not working correctly.
Expected input:
const input = { a: "foo", b: "bar", c: "", d: "baz", e: ""}
Expected output:
const foobar = { a: "foo", b: "bar", c: "N/A", d: "baz", e: "N/A"}
The map operator you are using could be employed if you want a completely new object. If that is the case though, you would have to deal with cloning the original object. Deep cloning with JS objects can be tricky though if your object has nested objects or arrays as a part of it. If you don't need a completely new object, then updating the properties in place on the original object could be done fairly easily as below.
const input = { a: "foo", b: "bar", c: "", d: "baz", e: ""}
Object.keys(input).forEach(key => input[key] = input[key] ? input[key] : 'N/A');
console.log(input);
const input = {
a: "foo",
b: "bar",
c: "",
d: "baz",
e: "",
f: {
g: "",
h: "bfoo"
}
};
function NAReplacer(obj) {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => {
if (typeof value === 'string') {
return [key, value == '' ? 'N/A' : value];
}
if (Object.prototype.toString.call(value) === '[object Object]') {
return [key, NAReplacer(value)];
}
}));
}
console.log(NAReplacer(input));
Just loop over the keys of the object
const isFieldEmpty = (data) => {
const newObj = { ...data };
Object.keys(newObj).forEach((i) => {
if (newObj[i] === "") {
newObj[i] = "N/A";
}
});
return newObj;
};
const input = { a: "foo", b: "bar", c: "", d: "baz", e: "" };
console.log(input);
// { a: "foo", b: "bar", c: "", d: "baz", e: "" }
console.log(isFieldEmpty(input));
// { a: "foo", b: "bar", c: "N/A", d: "baz", e: "N/A" }
Your solution was almost there. From your code
key.value = 'N/A';
It's not the key.value that you want to change but data[keys] = 'N/A';
In other words, how to turn original into expected, or how to set the values of keys which values are empty strings with a dot notation formatted representation of the key's "path" inside the object ?
original = {
A: {
B: {
C: {
D: "",
E: ""
},
F: {
G: "",
H: ""
}
}
}
}
expected = {
A: {
B: {
C: {
D: "A.B.C.D",
E: "A.B.C.E
},
F: {
G: "A.B.F.G",
H: "A.B.F.H"
}
}
}
}
Surely people must have stumbled on this kind of issue when dealing with "stringly-typed" systems ?
It can be done in that way:
const original = {
A: {
B: {
C: {
D: "",
E: ""
},
F: {
G: "",
H: ""
}
}
}
}
const addPath = (data, path = []) => {
return Object.keys(data).reduce((acc, key) => {
const currentPath = [...path, key]
return {
...acc,
[key]: typeof data[key] === 'object'
? addPath(data[key], currentPath)
: currentPath.join('.')
}
}, {})
}
console.log(addPath(original))
I am writing a function for calculating the depth of an object.
Here is my recursive version which seems to work as expected:
function findDepth(obj, firstCall = true) {
if (firstCall && typeof obj !== "object") {
return -1;
}
return Object.keys(obj).reduce((max, k) => {
if (typeof obj[k] === "object" && obj[k] !== null) {
const val = findDepth(obj[k], false) + 1;
if (val > max) {
max = val;
}
}
return max;
}, 1);
}
const input1 = {
a: {
b: "test",
c: {
d: {
e: {
f: [1, 2, 3],
g: {
a: null,
z: {
d: "casdsadasdsa",
q: {
z: {
i: undefined
}
}
}
}
}
},
c: {
a: "sad"
}
},
d: {
e: 5
}
},
b: {
c: {
d: "dsada"
}
}
};
const input2 = {
w: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk",
q: {
w: {
z: "dsajkdasjdla"
}
}
},
h: "dsiaodsiad"
}
},
a: "test",
b: "test2",
c: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk"
},
h: "dsiaodsiad"
}
},
e: "bye"
};
console.log(findDepth(input1));
console.log(findDepth(input2));
Now I am trying to write an iterative version, but I cannot find the best way to make the loop work.
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let max = 1;
let copy = Object.assign({}, obj);
let keys = Object.keys(copy);
while (keys.length) {
if (typeof copy[keys[0]] !== "object" && copy[keys[0]] !== null) {
delete copy[keys[0]];
keys = Object.keys(copy);
} else {
max++;
copy = Object.assign({}, copy[keys[0]]);
keys = Object.keys(copy);
}
}
return max;
}
const input1 = {
a: {
b: "test",
c: {
d: {
e: {
f: [1, 2, 3],
g: {
a: null,
z: {
d: "casdsadasdsa",
q: {
z: {
i: undefined
}
}
}
}
}
},
c: {
a: "sad"
}
},
d: {
e: 5
}
},
b: {
c: {
d: "dsada"
}
}
};
const input2 = {
w: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk",
q: {
w: {
z: "dsajkdasjdla"
}
}
},
h: "dsiaodsiad"
}
},
a: "test",
b: "test2",
c: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk"
},
h: "dsiaodsiad"
}
},
e: "bye"
};
console.log(findDepthIterative(input1));
console.log(findDepthIterative(input2));
As you can see from the output and the code, it just takes the first property inside the loop:
while (keys.length) {
if (typeof copy[keys[0]] !== "object" && copy[keys[0]] !== null) {
delete copy[keys[0]];
keys = Object.keys(copy);
} else {
max++;
copy = Object.assign({}, copy[keys[0]]);
keys = Object.keys(copy);
}
}
The idea was to delete the property each iteration, but I am not getting in the right way.
I tried to change it with copy[keys[keys.length - 1]] but in this way it takes only the last property instead.
Actually the issue is how to loop over all the keys, on all the depth levels, as in the recursive version.
Any suggestion about how to implement this algorithm in an iterative way?
Even any suggestion on how to improve the recursive one (or if I am missing something) is more than welcome.
p.s. NO LOADASH, UNDERSCORE, RAMDA, or whatever. Just Vanilla JS
You just need to keep a stack and push children into it while keeping track of the current depth. You can keep track of that by pushing an array of [depth, obj] into the stack and then when you pop() add one to the depth before pushing children.
const input1 = {w: {d: "hello",f: {g: "dsadas",z: {b: "dsajkdasjldk",q: {w: {z: "dsajkdasjdla"}}},h: "dsiaodsiad"}},a: "test",b: "test2",c: {d: "hello",f: {g: "dsadas",z: {b: "dsajkdasjldk"},h: "dsiaodsiad"}},e: "bye"};
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let max = 0;
// current depth, children
let stack = [[0, Object.values(obj)]];
while(stack.length){
let [depth, cur] = stack.pop()
if (depth > max) max = depth
if (typeof cur === "object" && cur !== null){
Object.values(cur).forEach(c => stack.push([depth+1, c]))
}
}
return max
}
console.log(findDepthIterative(input1))
// sanity check:
const depth0 = {}
const depth1 = {a:1}
const depth2 = {a:{b:2}}
console.log(findDepthIterative(depth0))
console.log(findDepthIterative(depth1))
console.log(findDepthIterative(depth2))
One way to iterate could be a depth first search using a stack.
function f(obj){
let stack = Object.keys(obj).map(k => [obj[k], 1]);
let max = 0;
while (stack.length){
let [maybeObj, d] = stack.pop();
max = Math.max(max, d);
if (typeof maybeObj == 'object' && maybeObj !== null)
Object.keys(maybeObj).map(k =>
stack.push([maybeObj[k], d + 1]));
}
return max;
}
We could also make the recursion slightly more succinct:
function f(obj){
if (typeof obj !== 'object' || obj === null)
return 0;
return Object.keys(obj).reduce((acc, k) =>
Math.max(acc, 1 + f(obj[k])), 0);
}
You should use an array instead of the object which won't work properly if there are duplicate keys. The array should contain all the objects that occur at a certain level. For each iteration, you map the array into a new one containing the previous objects' direct children:
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let arr = [obj]; // the array containing the objects that occur at a certain level, initially contains obj being the only object at the first level
let levels = 0; // levels counter
do { // keep doing this
levels++; // increment the levels counter
let newArr = []; // make a new array for the next level
arr.forEach(obj => { // populate it with the old level objects' children
for(let key in obj) {
if(obj[key] && typeof obj[key] === "object") {
newArr.push(obj[key]);
}
}
});
arr = newArr; // make arr the new array of object (next level)
} while (arr.length); // while there are still levels with objects in them
return levels;
}
Demo:
function findDepthIterative(obj) {
if (typeof obj !== "object") {
return -1;
}
let arr = [obj];
let levels = 0;
do {
levels++;
let newArr = [];
arr.forEach(obj => {
for(let key in obj) {
if(obj[key] && typeof obj[key] === "object") {
newArr.push(obj[key]);
}
}
});
arr = newArr;
} while (arr.length);
return levels;
}
const input1 = {
a: {
b: "test",
c: {
d: {
e: {
f: [1, 2, 3],
g: {
a: null,
z: {
d: "casdsadasdsa",
q: {
z: {
i: undefined
}
}
}
}
}
},
c: {
a: "sad"
}
},
d: {
e: 5
}
},
b: {
c: {
d: "dsada"
}
}
};
const input2 = {
w: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk",
q: {
w: {
z: "dsajkdasjdla"
}
}
},
h: "dsiaodsiad"
}
},
a: "test",
b: "test2",
c: {
d: "hello",
f: {
g: "dsadas",
z: {
b: "dsajkdasjldk"
},
h: "dsiaodsiad"
}
},
e: "bye"
};
console.log(findDepthIterative(input1));
console.log(findDepthIterative(input2));
Suppose I have some object like:
const someObj = {
x: null,
y: {
z: null
},
a: {
b: {
c: null
}
}
}
I would like to create a function to set values using something like:
const setKV = (obj, ...keyArray) => {
/* Not quite sure how to phrase this function */
const val = keyArray.pop()
}
Such that I can set the values:
x with setKV(someObj, 'x', true)
z with setKV(someObj, 'y', 'z', true)
c with setKV(someObj, 'a', 'b', 'c', true)
How would I define an object's nested key by this arbitrary number of parameters?
You can do this easily using a rest parameter and spread argument like ...rest below. setKV does not mutate its input object, o; a new object is always returned.
const setKV = (o = {}, key, value, ...rest) =>
rest.length === 0
? { ...o, [key]: value }
: { ...o, [key]: setKV (o[key], value, ...rest) }
console.log
( setKV ({ a: 0 }, 'b', 1)
// { a: 0, b: 1 }
, setKV ({ a: { b: { c: false } } }, 'a', 'b', 'c', true)
// { a: { b: { c: true } } }
, setKV ({}, 'a', 'b', 'c', 1)
// { a: { b: { c: 1 } } }
, setKV ({ a: { b: { c: 0 } } }, 'a', 'b', 'd', 0)
// { a: { b: { c: 0, d: 0 } } }
, setKV ({ a: { b: { c: 0 } } }, 'a', 'b', 1)
// { a: { b: 1 } }
, setKV ({ a: 1, b: { c: 2, d: { e: 3 } } }, 'b', 'd', 'e', { f: 4 })
// { a: 1, b: { c: 2, d: { e: { f: 4 } } } }
, setKV ({ a: 0 }, 'b')
// { a: 0, b: undefined }
)
"If I did want to mutate the input object ..."
While mutations should be avoided, the specific needs of your program may warrant their use. In such a case, review mutKV, if only to see how it differs from the implementation above
const mutKV = (o = {}, key, value, ...rest) =>
rest.length === 0
? (o[key] = value, o)
: (o[key] = mutKV (o[key], value, ...rest), o)
const data =
{ a: 0 }
mutKV (data, 'b', 1)
console.log (data)
// { a: 0, b: 1 }
mutKV (data, 'c', 'd', 2)
console.log (data)
// { a: 0, b: 1, c: { d: 2 } }
mutKV (data, 'c', 'd', 3)
console.log (data)
// { a: 0, b: 1, c: { d: 0 } }
mutKV (data, 'c', 4)
console.log (data)
// { a: 0, b: 1, c: 4 }
This opens the book for a short lesson about side effects, and encoding them using effect. Below we create an effect mut using effect, then mut is used in each branch of mutKV. The behavior of the program is identical to mutKV above.
const effect = f => x =>
(f (x), x)
const mut = (key, value) =>
effect (o => o[key] = value)
const mutKV = (o = {}, key, value, ...rest) =>
rest.length === 0
? mut (key, value) (o)
: mut (key, mutKV (o[key], value, ...rest)) (o)
lodash provides what you're looking for, no need to rewrite it
https://lodash.com/docs/4.17.10#set
_.set(someObj, ['a', 'b', 'c'], true);
We can create function where rest params are two element arrays where first item is key name and secound is desired value
function setKV(obj, ...kvs) {
return kvs.reduce((obj, [k, v]) => {
obj[k] = v;
return obj;
}, obj);
}
function setKV(obj, ...kvs) {
return kvs.reduce((obj, [k, v]) => {
obj[k] = v;
return obj;
}, obj);
}
console.log(setKV({}, [
"a",
1
], [
"b",
"b"
]));