I'm trying to find a solution to a problem where I need to remove undefined from nested object including all parents if there are no values there, please consider example:
var test = {
foo : {
bar : {
baz : undefined
}
},
bar : 1
}
So my task is to remove baz along with bar and foo but still have bar at the root level;
I know that it's trivial task to solve with 2 for loops, I'm just wondering if there are more elegant and clean solutions which will use recursive stack instead?
Thanks in advance!
Depth-first recursion should be able to handle it:
function cleanse(obj, path) {
Object.keys(obj).forEach(function(key) {
// Get this value and its type
var value = obj[key];
var type = typeof value;
if (type === "object") {
// Recurse...
cleanse(value);
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
if (!Object.keys(value).length) {
delete obj[key]
}
}
else if (type === "undefined") {
// Undefined, remove it
delete obj[key];
}
});
}
Example:
var test = {
foo : {
bar : {
baz : undefined
}
},
bar : 1
};
cleanse(test);
function cleanse(obj, path) {
Object.keys(obj).forEach(function(key) {
// Get this value and its type
var value = obj[key];
var type = typeof value;
if (type === "object") {
// Recurse...
cleanse(value);
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
if (!Object.keys(value).length) {
delete obj[key]
}
}
else if (type === "undefined") {
// Undefined, remove it
delete obj[key];
}
});
}
console.log(test);
Note that that only visits own, enumerable properties of the objects whose names are not Symbols (ES2015+). If you also want to handle properties inherited from prototypes, or non-enumerable properties, or properties whose names are Symbols, you'll need to adjust to handle that. (You can get non-enumerable properties on an ES5 or later JavaScript engine via getOwnPropertyNames.)
Below example can help you get started.
Without delete keys with empty values:
var test = {
foo: {
bar: {
baz: undefined,
bar: {
baz: undefined
}
}
},
bar: 1,
baz: undefined
}
function loop(obj) {
var t = obj;
for (var v in t) {
if (typeof t[v] == "object")
loop(t[v]);
else if (t[v] == undefined)
delete t[v];
}
return t;
}
var output = loop(test);
console.log(output);
Deleting keys with empty values:
var test = {
foo: {
bar: {
baz: undefined,
bar: {
baz: undefined
}
}
},
bar: 1,
baz: undefined
}
function loop(obj) {
var t = obj;
for (var v in t) {
if (typeof t[v] == "object")
if (!t[v].length)
delete t[v];
else
loop(t[v]);
else if (t[v] == undefined)
delete t[v];
}
return t;
}
var output = loop(test);
console.log(output);
IMO this is much cleaner but probably a smidge slower
const cleanUndefined = object => JSON.parse(JSON.stringify(object));
const testWithoutUndefined = cleanUndefined(test)
I took the function proposed by #T.J. Crowder and changed it to use for ... of and Object.entries(obj). I also return the object for convenience.
function cleanseObject(obj) {
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
cleanseObject(value)
if (!Object.keys(value).length) delete obj[key]
} else if (typeof value === 'undefined') {
delete obj[key]
}
}
return obj
}
function cleanPayload(obj: any) {
Object.keys(obj).forEach(key => {
const value = obj[key];
const type = typeof value;
if (!value || !type) {
delete obj[key];
} else if (type === 'object') {
cleanPayload(value);
if (!Object.keys(value).length) {
if (key != 'attributes') {
delete obj[key];
}
}
}
});
return obj;
}
Without mutating the original object
const cleanse = obj => {
const newObj = Array.isArray(obj) ? [...obj] : { ...obj };
Object.keys(newObj).forEach((key) => {
// Get this value and its type
const value = newObj[key];
var type = typeof value;
if (type === "object") {
// Recurse...
newObj[key] = cleanse(value);
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here)
if (!Object.keys(value).length) {
delete newObj[key]
}
}
else if (type === "undefined") {
// Undefined, remove it
delete newObj[key];
}
});
return newObj;
};
console.log(
cleanse({ a: { b: undefined, c: 22 }}),
cleanse({ a: [{ b: undefined, c: 22 }] }),
);
Here is the code, which will also remove that undefined contained key and empty object from the main object.
var test = {
foo: {
bar: {
baz: undefined,
bar: {
baz: undefined,
}
}
},
bar: 1,
baz: undefined
}
function loop(obj) {
var t = obj;
for (var v in t) {
if (typeof t[v] == "object"){
loop(t[v]);
if(!Object.keys(t[v]).length){
delete t[v];
}
} else if (t[v] == undefined){
delete t[v];
}
}
return t;
}
var output = loop(test);
console.log(output);
Related
I am sure there is a name for what I am trying to do and I am sure this has been asked before, but I am struggling to find an existing answer - probably because I am not searching for the correct term. I have a JavaScript function called extend() which is similar to Object.assign() to merge Objects together - I don't have access to Object.assign in my current implementation.
function extend(target) {
for (var i = 1; i < arguments.length; ++i) {
if (typeof arguments[i] !== 'object') {
continue;
}
for (var j in arguments[i]) {
if (arguments[i].hasOwnProperty(j)) {
if (typeof arguments[i][j] === 'object') {
target[j] = extend({}, target[j], arguments[i][j]);
}
else if (!target.hasOwnProperty(j)) {
target[j] = arguments[i][j];
}
}
}
}
return target;
}
This works fine if I call it using extend(target, defaults) which will merge the contents of defaults into target without overwriting any existing keys in target (unless they are Objects and then they are merged).
I am looking for a way to actually call this using target.extend(defaults), so the first target parameter to extend is actually implied as this. I have tried to rewrite the function by replacing target with this, but it doesn't have the desired result (I think the way I have changed the recursion is wrong).
target.extend = function() {
...
this[j] = this.extend(this[j], arguments[i][j]);
...
return this;
};
Any pointers (or references to existing answers) would be gratefully appreciated.
You can assign the function to Object.prototype to achieve the functionality you're looking for:
Object.defineProperty(
Object.prototype,
'extend',
{
enumerable: false,
value: function() {
const target = this;
for (var i = 0; i < arguments.length; ++i) {
if (typeof arguments[i] !== 'object') {
continue;
}
for (var j in arguments[i]) {
if (arguments[i].hasOwnProperty(j)) {
if (typeof arguments[i][j] === 'object') {
target[j] = extend({}, target[j], arguments[i][j]);
}
else if (!target.hasOwnProperty(j)) {
target[j] = arguments[i][j];
}
}
}
}
return target;
}
}
);
const obj = { foo: 'foo' };
obj.extend({ bar: 'bar' });
console.log(obj);
Or, cleaning up the code a bunch:
Object.defineProperty(
Object.prototype,
'extend',
{
enumerable: false,
value: function(...args) {
const target = this;
for (const arg of args) {
for (const [key, val] of Object.entries(arg)) {
if (typeof val === 'object') extend(target[key], val);
else target[key] = val;
}
}
}
}
);
const obj = { foo: 'foo' };
obj.extend({ bar: 'bar' });
console.log(obj);
But this is a bad idea, because it can result in name collisions and confusing code. What if there's an object like
const obj = { extend: true };
What does obj.extend do then? (It'll refer to the own property, but this isn't something a script-writer should have to worry about. Better to have a standalone function.)
This is exactly the same reason why many of the Object helper methods exist on the Object constructor, and not on Object.prototype. For example, you call Object.assign, or Object.defineProperty, not obj.assign or obj.defineProperty.
If you want this functionality only on certain objects, you can avoid the prototype pollution by calling Object.defineProperty on those objects:
const addExtend = obj => {
Object.defineProperty(
obj,
'extend',
{
enumerable: false,
value: function(...args) {
const target = this;
for (const arg of args) {
for (const [key, val] of Object.entries(arg)) {
if (typeof val === 'object') extend(target[key], val);
else target[key] = val;
}
}
}
}
);
};
const obj = { foo: 'foo' };
addExtend(obj);
obj.extend({ bar: 'bar' });
console.log(obj);
Or, if you have control over where the object is created, you can use Object.create so that extend is a method inherited from the prototype:
const objExtendProto = {};
Object.defineProperty(
objExtendProto,
'extend',
{
enumerable: false,
value: function(...args) {
const target = this;
for (const arg of args) {
for (const [key, val] of Object.entries(arg)) {
if (typeof val === 'object') extend(target[key], val);
else target[key] = val;
}
}
}
}
);
const obj = Object.create(objExtendProto);
obj.foo = 'foo';
obj.extend({ bar: 'bar' });
console.log(obj);
Like #CertainPerformance has pointed out, what you're looking for will still create name collisions. But this is exactly what you wanted. You wanted to add a custom extend method in your target object right, then that's what we will do.
Explanation in comments
function extend() {
for (let i = 0; i < arguments.length; i++) { //loop through arguments
for (k in arguments[i]) { //loop argument props
if (typeof arguments[i][k] === 'object') {
this[k].extend = this.extend; //attach extend function to object prop
this[k].extend(arguments[i][k]) //execute extend recursively
}
else this[k] = this[k] || arguments[i][k]; //assign property to target while avoiding overwriting target object
}
}
delete this.extend; //delete extend method
return this; //return target object OR Object.create(this) to avoid mutating
}
//test
const trgt = { a: { a1: 2 }, b: 3, c: 5 };
const defaults = { b: 2, a: { a2: 1, a1: 55 }, d: 10 };
trgt.extend = extend; //assign extend method to target object
const result = trgt.extend(defaults); //execute
console.log(result);
I have already reviewed some of the answers to similar questions, however, I want to ask my question differently.
Let's say we have a string like "level1.level2.level3. ..." that indicates a nested property in an object called Obj.
The point is that we may not know how many nested properties exist in this string. For instance, it may be "level1.level2" or "level1.level2.level3.level4".
Now, I want to write a function, that given the Obj and the string of properties as input, to simply tell us if such a nested property exists in the object or not (let's say true or false as output).
Update:
Thanks to #Silvinus, I found the solution with a minor modification:
private checkNestedProperty(obj, props) {
var splitted = props.split('.');
var temp = obj;
for (var index in splitted) {
if (temp[splitted[index]] === 'undefined' || !temp[splitted[index]]) return false;
temp = temp[splitted[index]];
}
return true;
}
You could use Array#every() and thisArg of it, by iterating the keys and checking if it is in the given object.
var fn = function (o, props) {
return props.split('.').every(k => k in o && (o = o[k], true));
}
console.log(fn({}, "toto.tata")); // false
console.log(fn({ toto: { tata: 17 } }, "toto.tata")); // true
console.log(fn({ toto: { tata: { tutu: 17 } } }, "toto.foo.tata")); // false
console.log(fn({ toto: { tata: false } }, "toto.tata")); // true
You can explore your Obj with this function :
var fn = function(obj, props) {
var splited = props.split('.');
var temp = obj;
for(var index in splited) {
if(typeof temp[splited[index]] === 'undefined') return false;
temp = temp[splited[index]]
}
return true
}
var result = fn({ }, "toto.tata");
console.log(result); // false
var result = fn({ toto: { tata: 17 } }, "toto.tata");
console.log(result); // true
var result = fn({ toto: { tata: { tutu: 17 } } }, "toto.foo.tata");
console.log(result); // false
This function allow to explore nested property of Obj that depends of props passed in parameter
This answer provides the basic answer to your question. But it needs to be tweaked to handle the undefined case:
function isDefined(obj, path) {
function index(obj, i) {
return obj && typeof obj === 'object' ? obj[i] : undefined;
}
return path.split(".").reduce(index, obj) !== undefined;
}
Based on the solution given by #Silvinus here is a solution if you deal with array inside nested objects (as it is often the case in results from databases queries) :
checkNested = function(obj, props) {
var splited = props.split('.');
var temp = obj;
for(var index in splited) {
var regExp = /\[([^)]+)\]/;
var matches = regExp.exec(splited[index])
if(matches) {
splited[index] = splited[index].replace(matches[0], '');
}
if(matches) {
if(matches && typeof temp[splited[index]][matches[1]] === 'undefined') return false;
temp = temp[splited[index]][matches[1]];
}
else {
if(!matches && typeof temp[splited[index]] === 'undefined') return false;
temp = temp[splited[index]]
}
}
return true
}
obj = {ok: {ao: [{},{ok: { aa: ''}}]}}
console.log(checkNested(obj, 'ok.ao[1].ok.aa')) // ==> true
console.log(checkNested(obj, 'ok.ao[0].ok.aa')) // ==> false
I'm trying to make a copy of an object that only includes the properties that are not objects. But the child objects get copied along with it.
var testObject = {
stringProperty: "hi",
intProperty: 4,
objectProperty: {},
nullProperty: null
};
console.log(removeChildObjects(testObject));
function removeChildObjects(object) {
var keys = Object.keys(object);
var newObject = {};
keys.forEach(function(key) {
console.log(key, object[key], typeof object[key]);
if (typeof object[key] != "object") {
newObject[key] = object[key];
}
});
return object;
}
Also check it out here https://jsfiddle.net/uss94sc3/1/
If you want to strictly filter out object properties (keeping null and undefined properties), then you cannot rely on the broken typeof unary operator.
typeof null
// "object"
You can either change your code to:
function removeChildObjects(object) {
var keys = Object.keys(object);
var newObject = {};
keys.forEach(function(key) {
if (typeof object[key] != "object" || object[key] == null) {
newObject[key] = object[key];
}
});
return newObject;
}
or more succinctly with underscore:
function removeChildObjects(object) {
return _.omit(object, _.isObject);
}
You return the same object that passed:
return object;
You should return newObject
return newObject;
Try replacing return object; with return newObject;. It will work a lot better!
https://jsfiddle.net/w3urvpjq/
You may try this
var testObject = {
stringProperty: "hi",
intProperty: 4,
objectProperty: {},
nullProperty: null
};
var filterPrimitive = o => Object.keys(o).reduce((p,k) => {typeof o[k] != "object" && (p[k] = o[k]); return p},{});
document.write("<pre>" + JSON.stringify(filterPrimitive(testObject),null,2) + "</pre>");
I am trying to write a function that digs an object till it gets to the last .value or .content property. I wrote this and for the life of me I cant figure out why it breaks.
var jscGetDeepest = function(obj) {
try {
console.info(Math.round(Math.random() * 10) + ' starting jscGetDeepest:', obj, obj.toString());
} catch(ignore) {}
while (obj && ('contents' in obj || 'value' in obj)) {
if ('contents' in obj) {
obj = obj.contents;
} else if ('value' in obj) {
obj = obj.value;
}
//console.info('loop jscGetDeepest:', obj.toString());
}
if (obj || obj === 0) {
obj = obj.toString();
}
console.info('finaled jscGetDeepest:', obj);
return obj;
}
The issue occurs when inner value in the next iteration is not an object. In this case you get an error message, because in operand can't be used with primitives.
To fix it check for object before trying to get deeper. Here is a fixed an slightly improved version with JSON.stringify instead of toString (maybe better to return object itself without stringifying it?):
var jscGetDeepest = function (obj) {
while (typeof obj === 'object' && obj !== null && ('contents' in obj || 'value' in obj)) {
if ('contents' in obj) {
obj = obj.contents;
} else if ('value' in obj) {
obj = obj.value;
}
}
if (typeof obj === 'object') {
obj = JSON.stringify(obj);
}
return obj;
}
alert( jscGetDeepest({value: {name: 2, contents: {name: 3, value: 23}}}) );
alert( jscGetDeepest({value: {name: 2, value: {name: 3, contents: {name: 4}}}}) );
Let's assume that there is an app written in react.js. It fetches single json through rest api and pass some properties to children components.
What is a proper way to handle potentially missing attributes of json and stuff? I probably should check in every component if props are there, and fill state with proper structure but filled with empty data, something like this:
var SomeComponent = React.createClass({
getInitialState: function() {
return {
someNestedStructure: {
foo: {
bar: null,
baz: null
},
morenested: {
something: '',
andEvenMoreNested: {
somethingb: ''
}
},
somedata: {
id: null
},
somedataaa: {
}
}
}
},
componentDidMount: function() {
//call rest api and set new state depending on what is inside json
//check every required field to pass to children compoents
},
render: function() {
return (
<div>
<ComponentUsingNEsteStructure data={this.state.someNestedStructure.moreNested}/>
<ComponentThatNeedsEverythign data={this.state.someNestedStructure} />
<SomeOtherComponent some={this.sate.somedataaa} />
</div>
);
}
});
But I guess it generate a lot of json related structures inside code and a lot of ifs.
Thanks for any help!
This isn't specific to React. What you want is deep extend. This is available on npm as deep-extend
The usage is like this:
var defaultsForThing = {
foo: '',
bar: {
baz: true,
quux: null
}
};
// make a defaulting function
var defaultThing = deepExtend.bind(null, defaultsForThing);
// fetch some data which might be missing fields
$.getJSON('/api', function(data){
var fixed = defaultThing(data);
doSomethingWith(fixed);
});
Here's the deepExtend function (but prefer the linked to module)
var deepExtend = module.exports = function (/*obj_1, [obj_2], [obj_N]*/) {
if (arguments.length < 1 || typeof arguments[0] !== 'object') {
return false;
}
if (arguments.length < 2) return arguments[0];
var target = arguments[0];
// convert arguments to array and cut off target object
var args = Array.prototype.slice.call(arguments, 1);
var key, val, src, clone, tmpBuf;
args.forEach(function (obj) {
if (typeof obj !== 'object') return;
for (key in obj) {
if ( ! (key in obj)) continue;
src = target[key];
val = obj[key];
if (val === target) continue;
if (typeof val !== 'object' || val === null) {
target[key] = val;
continue;
} else if (val instanceof Buffer) {
tmpBuf = new Buffer(val.length);
val.copy(tmpBuf);
target[key] = tmpBuf;
continue;
} else if (val instanceof Date) {
target[key] = new Date(val.getTime());
continue;
} else if (val instanceof RegExp) {
target[key] = new RegExp(val);
continue;
}
if (typeof src !== 'object' || src === null) {
clone = (Array.isArray(val)) ? [] : {};
target[key] = deepExtend(clone, val);
continue;
}
if (Array.isArray(val)) {
clone = (Array.isArray(src)) ? src : [];
} else {
clone = (!Array.isArray(src)) ? src : {};
}
target[key] = deepExtend(clone, val);
}
});
return target;
}
One thing I might change in that function is that it doesn't satisfy this case well:
var defaults = {
a: [{b: 0, c: 0}]
};
var obj = {
a: [{b: 1}, {b: 2}, {c: 3}]
};
deepExtend(defaults, obj);
For the purpose of fixing json responses, you'd want to end up with the following, however this isn't technically a deep default (it's schema coercion).
{
a: [{b: 1, c: 0}, {b: 2, c: 0}, {c: 3, b: 0}]
}
You can avoid the large, nested object at the top level by using getDefaultProps to assign defaults to each component's props to cater for missing values. You could move the REST API access to the parent and pass the results to the children via their props (you'd have to use the parent's state as a trigger to re-render the child on getting the AJAX response, though).