I want use extend a javascript object, upto all possible nested levels.
I am using this function (the returned function) as iterator
var ObjWalk = (function( GLOBAL_APP_CONFIG,GLOBAL_METHODS){
if (typeof GLOBAL_APP_CONFIG !== 'object' || GLOBAL_APP_CONFIG === null) GLOBAL_APP_CONFIG = {};
const maxobjdepth = GLOBAL_APP_CONFIG.maxobjdepth || 99;
const endvar = GLOBAL_APP_CONFIG.walkendkey || '$W_END';
let ifEndForObjWalk = GLOBAL_METHODS && GLOBAL_METHODS.ifEndForObjWalk;
if(typeof ifEndForObjWalk !== 'function') {
ifEndForObjWalk = function(obj, depth) {
return ((depth < maxobjdepth && typeof obj === 'object'
&& obj !== null && obj[endvar] !== true) ? obj : false);
};
};
const walkInto = function(fun, rt, obj, key, depth, isLast) {
if(!depth) depth = 0;
fun(obj, key, rt, depth || 0, typeof isLast === 'boolean' ? isLast : true);
const ob = ifEndForObjWalk(obj, depth);
if (ob) {
const kys = Object.keys(ob);
const lastln = kys.length - 1;
const deep = depth + 1;
for (let z = 0; z <= lastln; z += 1) {
walkInto(fun, ob, ob[kys[z]], kys[z], deep, (z === lastln));
}
}
};
return walkInto;
})();
The iterators simply iterates each and every possible values and call that value via a function, that is passed at calling.
I know there may be some other solutions available for deep extending javascript object, but i want to achieve deep extend via using this iterator only.
This is what i have done
const deepExtend = function(obj1, obj2) {
let ptr1 = obj1;
// resolve if new value should be array or object or no change
function resolve(ab, vl){
if ((typeof vl === 'object')
&& (ab === undefined || typeof ab !== 'object'
|| (vl === null && ab !== null)
|| (vl !== null && ab === null)
|| (Array.isArray(vl) && !Array.isArray(ab))
|| (!Array.isArray(vl) && Array.isArray(ab))
|| (vl.length > ab.length)
)
) {
ab = vl === null ? vl : (Array.isArray(vl) ? new Array(vl.length) : {});
}
return ab;
}
ptr1 = resolve(obj1, obj2);
if (typeof ptr1 !== 'object') return ptr1;
// ptrs1 = Array of pointers that will make the changes to a particular depth at a time.
const ptrs1 = [[null,true],[ptr1,true]];
// below vl = value, ky = key, ob = someOb where someOb[ky] = vl
// dpt = nested depth
// isLast = if the `ky` is the last key of someOb
ObjWalk(function(vl, ky, ob, dpt, isLast){
if (ky && dpt) {
// whenever a object found
if (typeof vl === 'object') {
ptr1[ky] = resolve(ptr1[ky], vl);
if(ptr1[ky] !== null) {
if (ptrs1[dpt+1]) {
// update if pointer already exists
ptrs1[dpt+1] = [ptr1[ky], isLast];
} else {
// or push a new pointer
ptrs1.push([ptr1[ky], isLast]);
}
ptr1 = ptr1[ky];
}
} else {
ptr1[ky] = vl;
if (isLast) {
let ind = dpt;
let cr = ptrs1[dpt];
// find the closest sub pointer that should change next
do {
ind--;
cr = ptrs1[ind];
ptr1 = cr[0];
} while (cr[1] !== true && ind);
}
}
}
}, null, obj2);
return obj1;
}
Sandbox for the one failure is here
https://jsfiddle.net/rfn3ejy0/5/
Its difficult to debug where is the issue.
Any help there?
Related
I have a problem with this structure:
const ob = {
name: ''
ob: {}
arr: []
}
I want to check if all values, are empty.
If I have only strings and arrays, the problem is trivial, but with an object my best solution is something like that,
const test = Object.values(ob).reduce((acc, curr) => {
const isPlainObject = typeof curr === 'object' && !Array.isArray(curr);
if (isPlainObject) !Object.values(curr).length ? (acc = false) : null;
else !curr.length ? (acc = false) : null;
return acc;
}, true);
I'm not satisfied with this, did anybody face similar problem and can help me with that?
You could check the various types and then either the length or the count of (own enumerable) keys or the value.
const
isEmpty = object => Object.values(object).every(v => {
if (Array.isArray(v)) return v.length === 0;
if (v && typeof v === 'object') return Object.keys(v).length === 0;
return v === '';
}),
ob = { name: '', ob: {}, arr: [] };
console.log(isEmpty(ob));
Maybe not so much better than your solution, C233. But I'll give it a shot ;)
const isEmpty = (val) => {
if(!val) {
return true;
}
if (typeof val === 'object') {
return Object.values(val).length === 0;
}
if (typeof val === 'string') {
return val.length === 0;
}
return false;
}
const hasEmptyProps = (obj) => Object.values.map(isEmpty).reduce((result, curr) => result && curr, true);
I wanted to find out if my object is empty or not for all its nested objects and key-value pairs.
for e.g.,
const x = {
a:"",
b:[],
c:{
x:[]
},
d:{
x:{
y:{
z:""
}
}
}
};
this should be an empty object and if any of this contains single value then it should be non empty.
Here is the way to do what using recursion
const x = {
a:"",
b:[],
c:{
x:[]
},
d:{
x:{
y:{
z:''
}
}
}
};
function checkEmpty(obj){
for(let key in obj){
//if the value is 'object'
if(obj[key] instanceof Object === true){
if(checkEmpty(obj[key]) === false) return false;
}
//if value is string/number
else{
//if array or string have length is not 0.
if(obj[key].length !== 0) return false;
}
}
return true;
}
console.log(checkEmpty(x))
x.d.x.y.z = 0;
console.log(checkEmpty(x));
You can write a recursive function like following. Function creates a set with 2 possible values true and false. If the size of set is 1 and the value being false, which mean that the object is empty.
const x = {a:"",b:[],c:{x:[]},d:{x:{y:{z:""}}}};
function isEmpty(o, r = new Set()) {
for (let k in o) {
if(typeof o[k] === "object") {
if(Array.isArray(o[k])) r.add(!!o[k].length);
else isEmpty(o[k],r);
} else r.add(!(o[k] === "" || o[k] === undefined || o[k] === null));
}
return r;
}
let result = isEmpty(x);
console.log(result.has(false) && result.size == 1);
I will use a recursive approach for this one, we iterate over the object.keys() and check is every value related to the key is empty, in the case the value is an object, we go one level deeper to check it.
const x = {
a:"",
b:[],
c:{x:[]},
d:{x:{y:{z:""}}}
};
const x1 = [0,0,0];
const x2 = {0:0,1:0,2:0};
const isEmpty = (obj, empty=true) =>
{
Object.keys(obj).forEach((key) =>
{
if (typeof obj[key] === "object")
empty = isEmpty(obj[key], empty);
else
empty = empty && (obj[key].length === 0);
// Return early if we detect empty here.
if (!empty) return empty;
});
return empty;
}
console.log("original x: ", isEmpty(x));
x.a = "I'm not empty";
console.log("x after edit: ", isEmpty(x));
console.log("x1: ", isEmpty(x1));
console.log("x2: ", isEmpty(x2));
try (we use here recursion, fat arrow, obj. keys, reduce, ternary operator and object checking)
let isEmpty = o => o.constructor.name === "Object" ?
Object.keys(o).reduce((y,z)=> y&&isEmpty(o[z]) ,true) : o.length == 0;
const x = {
a:"",
b:[],
c:{
x:[]
},
d:{
x:{
y:{
z:""
}
}
}
};
let isEmpty = o => o.constructor.name === "Object" ?
Object.keys(o).reduce((y,z)=> y&&isEmpty(o[z]) ,true) : o.length == 0;
// Test
console.log(isEmpty(x));
x.d.x.y.z="Smile to life and life will smile to you";
console.log(isEmpty(x));
This question already has answers here:
What is the most efficient way to deep clone an object in JavaScript?
(67 answers)
Closed 4 years ago.
how can I deep clone an object, what could be wrong with this solution.
I wrote this decision, but I'm not sure if this is good, and what bottlenecks it has.
How to do it correctly on vanilla js, without using jQuery. If the object has (enumerable: false)?
let user = {
name: 'SomeName',sayHi: function(){console.log(this.name);}}
Object.defineProperty(user, 'sayHi', {enumerable:false});
function deepCloneNew(obj){
if (!obj) { return };
let cloneObj = {};
let keys = Object.getOwnPropertyNames(obj);
keys.forEach((key)=>{
if(typeof obj[key] === 'object' && obj[key] !== null){
deepCloneNew(obj[key]);
}
if(typeof obj[key] === 'function'){
Object.defineProperty(cloneObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
if(typeof obj[key] !== 'object' && typeof obj[key] !== 'function' || obj[key] === null){
Object.defineProperty(cloneObj, key, Object.getOwnPropertyDescriptor(obj, key));
}
})
return cloneObj;
}
let copy = deepCloneNew(user);
Please Follow this
function clone(item) {
if (!item) { return item; } // null, undefined values check
var types = [ Number, String, Boolean ],
result;
// normalizing primitives if someone did new String('aaa'), or new Number('444');
types.forEach(function(type) {
if (item instanceof type) {
result = type( item );
}
});
if (typeof result == "undefined") {
if (Object.prototype.toString.call( item ) === "[object Array]") {
result = [];
item.forEach(function(child, index, array) {
result[index] = clone( child );
});
} else if (typeof item == "object") {
// testing that this is DOM
if (item.nodeType && typeof item.cloneNode == "function") {
var result = item.cloneNode( true );
} else if (!item.prototype) { // check that this is a literal
if (item instanceof Date) {
result = new Date(item);
} else {
// it is an object literal
result = {};
for (var i in item) {
result[i] = clone( item[i] );
}
}
} else {
// depending what you would like here,
// just keep the reference, or create new object
if (false && item.constructor) {
// would not advice to do that, reason? Read below
result = new item.constructor();
} else {
result = item;
}
}
} else {
result = item;
}
}
return result;
}
This question already has answers here:
Remove empty elements from an array in Javascript
(49 answers)
Closed 5 years ago.
I'm having an issue with this function that recursively removes empty values from an object:
const _ = require('lodash')
function sanitize(object) {
Object.entries(object).forEach(([key, val]) => {
if (
val == null ||
Number.isNaN(val) ||
(typeof val === 'string' && isOnlyWhitespace(val)) ||
(typeof val === 'object' && Object.keys(sanitize(val)).length === 0)
) {
delete object[key]
}
});
// Remove `undefined` values leftover from using `delete` on an array.
if (Array.isArray(object)) {
_.pull(object, undefined); // THIS IS THE LINE IM TRYING TO CHANGE
}
return object;
}
function isOnlyWhitespace(str) {
return !(/\S/).test(str.trim());
}
I'm trying to replace _.pull(object, undefined) with vanilla JS, but nothing seems to give the right output (I've tried using stuff like filter.)
Here is a snippet you can run to see both outputs:
// LODASH VERSION
function lodashSanitize(object) {
Object.entries(object).forEach(([key, val]) => {
if (
val == null ||
Number.isNaN(val) ||
(typeof val === 'string' && isOnlyWhitespace(val)) ||
(typeof val === 'object' && Object.keys(lodashSanitize(val)).length === 0)
) {
delete object[key]
}
});
// Remove `undefined` values leftover from using `delete` on an array.
if (Array.isArray(object)) {
_.pull(object, undefined); // THIS IS THE LINE IM TRYING TO CHANGE
}
return object;
}
// MY VERSION
function mySanitize(object) {
Object.entries(object).forEach(([key, val]) => {
if (
val == null ||
Number.isNaN(val) ||
(typeof val === 'string' && isOnlyWhitespace(val)) ||
(typeof val === 'object' && Object.keys(mySanitize(val)).length === 0)
) {
delete object[key]
}
});
// Remove `undefined` values leftover from using `delete` on an array.
if (Array.isArray(object)) {
object = object.filter(val => val != null) // THIS IS MY ATTEMPT
}
return object;
}
function isOnlyWhitespace(str) {
return !(/\S/).test(str.trim());
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<button id="lodash">Show lodash output</button>
<button id="me">Show my output</button>
<p id="output" />
<script>
/**
* Fiddle-related code, you can ignore this
*/
const lodashBtn = document.querySelector('#lodash')
const meBtn = document.querySelector('#me')
const output = document.querySelector('#output')
function createExampleInput() {
const input = {
name: 'John',
grades: [
90,
undefined,
50,
null
]
};
return input;
}
lodashBtn.addEventListener('click', () => {
output.textContent = JSON.stringify(lodashSanitize(createExampleInput()), null, 4)
});
meBtn.addEventListener('click', () => {
output.textContent = JSON.stringify(mySanitize(createExampleInput()), null, 4)
});
</script>
The problem is that filter returns a new array. Why not just use a for loop and splice:
if (Array.isArray(object)) {
for (var i = object.length - 1; i >= 0; i--) {
if (object[i] === undefined) {
object.splice(i, 1);
}
}
}
So I have a fairly complex object like this:
var obj = {
v1:"ok",
v2:[
{av1:"foo", av2:null}, // notice there's a null here
{av1:"thing", av2:"stuff"}
],
v3: null,
v4:{
ov1:"slim",
ov2:"shady",
ov3:null // null
},
v5:[], // empty
v6:{} // empty
}
I'd like to get this back:
var obj = {
v1:"ok",
v2:[{av1:"foo"},{av1:"thing", av2:"stuff"}],
v4:{ov1:"slim",ov2:"shady"}
}
I'm trying to write a function that can delete anything that is null, undefined, or empty, but it's quickly becoming a spaghetti nightmare, and doesn't work.
I feel like there's a shorter more elegant way to do this, but this is my code so far:
function deleteNulls(o){
for(let i in o){
if(typeof o[i] == "Object"){
o[i] = deleteNulls(o[i])
}
else if(typeof o[i] == "Array"){
o[i] = deleteNulls(o[i])
}
if(o[i] == null || o[i] == "undefined" || o[i] == [] | o[i] == {})
delete o[i]
else {
if(typeof o == "Object"){
delete this.o[i]
return o
}
else if (typeof o == "Array")
return o.filter(k => o[k] != null)
}
}
return o
}
var obj = deleteNulls(obj)
I'm not interested in how to fix errors in the code above. I could get this to work if I wanted to,
I'm wondering if there's an easier way.
I'd suggest using something like lodash. It's tested and peer-reviewed for speed and efficiency.
Something like:
var result = _.omitBy(my_object, _.isNil);
This would remove all null values, you'll need to change the second parameter to remove empty objects and arrays.
It helped me writing this solution to split the logic into a recursive clean function (simplifying existing object structures) and a shouldKeep function (which tells you whether a key can be removed entirely from an object structure based on its value).
Demo Snippet:
var object = {
v1: "ok",
v2: [{
av1: "foo",
av2: null
}, // notice there's a null here
{
av1: "thing",
av2: "stuff"
}
],
v3: null,
v4: {
ov1: "slim",
ov2: "shady",
ov3: null // null
},
v5: [], // empty
v6: {} // empty
}
function shouldKeep (o) {
if (Array.isArray(o)) {
return o.length
} else if (typeof o === 'object') {
return o && Object.keys(o).length
}
return o != null
}
function clean (o) {
if (Array.isArray(o)) {
o.forEach(clean)
var a = o.filter(shouldKeep)
o.length = a.length
for (var i = 0; i < a.length; i++) {
o[i] = a[i]
}
} else if (o && typeof o === 'object') {
Object.keys(o).forEach(function (k) {
clean(o[k])
if (!shouldKeep(o[k])) delete o[k]
})
}
return o
}
console.log(clean(object))
.as-console-wrapper { min-height: 100vh; }
Here is my take at it. Should be good enough for most situations.
var object = {
v1: "ok",
v2: [{
av1: "foo",
av2: null
}, // notice there's a null here
{
av1: "thing",
av2: "stuff"
}
],
v3: null,
v4: {
ov1: "slim",
ov2: "shady",
ov3: null // null
},
v5: [], // empty
v6: {} // empty
}
function isEmpty(v) {
return v == undefined
|| v == null
|| (Array.isArray(v) && v.length === 0)
|| (typeof v === 'object' && Object.keys(v).length === 0)
}
function removeFalsyFromObject(v) {
var keys = Object.keys(v);
keys.forEach(function(k){
var val = v[k];
if (isEmpty(val)) {
delete v[k];
} else if (Array.isArray(val)) {
removeFalsyFromArray(val);
} else if (typeof val === 'object') {
removeFalsyFromObject(val);
}
})
}
function removeFalsyFromArray(a) {
for (var i=0; i<a.length; i++) {
var v = a[i];
if (isEmpty(v)) {
a.splice(i,1);
i--;
} else if (Array.isArray(v)) {
removeFalsyFromArray(v);
} else if (typeof v === 'object') {
removeFalsyFromObject(v);
}
}
}
function clean(obj) {
removeFalsyFromObject(obj);
return obj;
}
console.log(clean(object));