I have an object which contains an unknown number of other objects. Each (sub-)object may contain boolean values as strings and I want to change them to real boolean values. Here's an example object:
var myObj = {
my1stLevelKey1: "true",
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: "true",
my4thLevelKey2: "false"
}
},
my2ndLevelKey2: {
my3rdLevelKey2: "FALSE"
}
}
}
What I want in the end is this:
var myObj = {
my1stLevelKey1: true,
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: true,
my4thLevelKey2: false
}
},
my2ndLevelKey2: {
my3rdLevelKey2: false
}
}
}
Important is that the number sub-objects/levels is unknown. How can I do this effectively by either using classic JavaScript or Mootools?
Recursion is your friend
(function (obj) { // IIFE so you don't pollute your namespace
// define things you can share to save memory
var map = Object.create(null);
map['true'] = true;
map['false'] = false;
// the recursive iterator
function walker(obj) {
var k,
has = Object.prototype.hasOwnProperty.bind(obj);
for (k in obj) if (has(k)) {
switch (typeof obj[k]) {
case 'object':
walker(obj[k]); break;
case 'string':
if (obj[k].toLowerCase() in map) obj[k] = map[obj[k].toLowerCase()]
}
}
}
// set it running
walker(obj);
}(myObj));
The obj[k].toLowerCase() is to make it case-insensitive
Walk each level of the object and replace boolean string values with the appropriate booleans. If you find an object, recurse in and replace again.
You can use Object.keys to grab all the members of each object, without worrying about getting inherited properties that you shouldn't.
var myObj = {
my1stLevelKey1: "true",
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: "true",
my4thLevelKey2: "false"
}
},
my2ndLevelKey2: {
my3rdLevelKey2: "FALSE"
}
}
};
function booleanizeObject(obj) {
var keys = Object.keys(obj);
keys.forEach(function(key) {
var value = obj[key];
if (typeof value === 'string') {
var lvalue = value.toLowerCase();
if (lvalue === 'true') {
obj[key] = true;
} else if (lvalue === 'false') {
obj[key] = false;
}
} else if (typeof value === 'object') {
booleanizeObject(obj[key]);
}
});
}
booleanizeObject(myObj);
document.getElementById('results').textContent = JSON.stringify(myObj);
<pre id="results"></pre>
JavaScript data structures elegantly can be sanitized by recursive functional reduce approaches.
var myObj = {
my1stLevelKey1: "true",
my1stLevelKey2: "a normal string",
my1stLevelKey3: {
my2ndLevelKey1: {
my3rdLevelKey1: {
my4thLevelKey1: "true",
my4thLevelKey2: "false"
}
},
my2ndLevelKey2: {
my3rdLevelKey2: "FALSE"
}
}
};
myObj = Object.keys(myObj).reduce(function sanitizeBooleanStructureRecursively (collector, key) {
var
source = collector.source,
target = collector.target,
value = source[key],
str
;
if (value && (typeof value == "object")) {
value = Object.keys(value).reduce(sanitizeBooleanStructureRecursively, {
source: value,
target: {}
}).target;
} else if (typeof value == "string") {
str = value.toLowerCase();
value = ((str == "true") && true) || ((str == "false") ? false : value)
}
target[key] = value;
return collector;
}, {
source: myObj,
target: {}
}).target;
console.log(myObj);
Plain javascript recursion example:
function mapDeep( obj ) {
for ( var prop in obj ) {
if ( obj[prop] === Object(obj[prop]) ) mapDeep( obj[prop] );
else if ( obj[prop].toLowerCase() === 'false' ) obj[prop] = false;
else if ( obj[prop].toLowerCase() === 'true' ) obj[prop] = true;
}
};
And MooTools example, by extending the Object type with custom mapDeep() function:
Object.extend( 'mapDeep', function( obj, custom ) {
return Object.map( obj, function( value, key ) {
if ( value === Object( value ) )
return Object.mapDeep( value, custom );
else
return custom( value, key );
});
});
myObj = Object.mapDeep( myObj, function( value, key ) {
var bool = { 'true': true, 'false': false };
return value.toLowerCase() in bool ? bool[ value.toLowerCase() ] : value;
})
Related
Suppose I have an object with depth-N like:
food = {
'Non-Animal': {
'Plants' : {
'Vegetables': {
...
}
},
'Minerals' : {
...
}
},
'Animal': {
...
}
}
And I want to add in this object the category 'Fruits', but I have to search the object where 'Plants' are and then add it. So I don't want to do in one statement:
food['Non-Animal']['Plants']['Fruits'] = {};
Since I want to search first where it belongs.
How can I add the fruits category to the object while iterating through it? What I have so far is:
addCategory(food, category, parent);
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name) {
obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
addCategory(p, category, parent);
} else {
}
}
}
How can I fix this routine to do this or is there a better way to do this?
If I'm understanding you correctly, I think you'd want your function to define a variadic parameter that takes individual names of the path you wish to traverse and create if necessary.
Using .reduce() for this makes it pretty easy.
const food = {
'Non-Animal': {
'Plants': {
'Vegetables': {}
},
'Minerals': {}
},
'Animal': {}
}
console.log(addCategory(food, "Non-Animal", "Plants", "Fruits"));
console.log(addCategory(food, "Non-Animal", "Minerals", "Gold"));
function addCategory(obj, ...path) {
return path.reduce((curr, name) => {
if (!curr) return null;
if (!curr[name]) return (curr[name] = {});
return curr[name];
// More terse but perhaps less readable
// return curr ? curr[name] ? curr[name] : (curr[name]={}) : null;
}, obj);
}
console.log(JSON.stringify(food, null, 2));
Looks fine. However you might want to terminate after adding the prop:
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name){
return obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
if(addCategory(p, category, parent)) return true;
}
}
}
I see only one mistake: The recursive call of addCategory cannot find the parent-variable, cause it's called parent_name in your scope.
var food = {
'Non-Animal': {
'Plants' : {
'Vegetables': {
}
},
'Minerals' : {}
},
'Animal': {}
}
function addCategory(obj, category, parent_name) {
for (var key in obj) {
if (key == parent_name){
obj[key][category] = {};
}
var p = obj[key];
if (typeof p === 'object') {
addCategory(p, category, parent_name);
} else {
}
}
}
console.log(food);
addCategory(food, 'Fruits', 'Plants');
console.log(food);
You can use reduce to create function that will take key as string and object as value that you want to assign to some nested object.
var food = {"Non-Animal":{"Plants":{"Vegetables":{}},"Minerals":{}},"Animal":{}}
function add(key, value, object) {
key.split('.').reduce(function(r, e, i, arr) {
if(r[e] && i == arr.length - 1) Object.assign(r[e], value);
return r[e]
}, object)
}
add('Non-Animal.Plants', {'Fruits': {}}, food)
console.log(food)
I need to convert "flat object" like this (input data):
{
'prop1': 'value.1',
'prop2-subprop1': 'value.2.1',
'prop2-subprop2': 'value.2.2',
}
to immersion object like this (output data):
{
'prop1': 'value.1',
'prop2': {
'subprop1': 'value.2.1',
'subprop2': 'value.2.2'
}
}
Of course solution have to be prepare for no-limit deep level.
My solution does not work:
var inputData = {
'prop1': 'value.1',
'prop2-subprop1': 'value.2.1',
'prop2-subprop2': 'value.2.2',
};
function getImmersionObj(input, value) {
var output = {};
if ($.type(input) === 'object') { // first start
$.each(input, function (prop, val) {
output = getImmersionObj(prop.split('-'), val);
});
} else if ($.type(input) === 'array') { // recursion start
$.each(input, function (idx, prop) {
output[prop] = output[prop] || {};
output = output[prop];
});
}
return output;
}
console.log(getImmersionObj(inputData)); // return empty object
Can you help me find the problem in my code or maybe you know another one, better algorithm for conversion like my?
You could use a function for spliting the path to the value and generate new objects for it.
function setValue(object, path, value) {
var way = path.split('-'),
last = way.pop();
way.reduce(function (o, k) {
return o[k] = o[k] || {};
}, object)[last] = value;
}
var object = { 'prop1': 'value.1', 'prop2-subprop1': 'value.2.1', 'prop2-subprop2': 'value.2.2' };
Object.keys(object).forEach(function (key) {
if (key.indexOf('-') !== -1) {
setValue(object, key, object[key]);
delete object[key];
}
});
console.log(object);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var input = {
"property1": "value1",
"property2.property3": "value2",
"property2.property7": "value4",
"property4.property5.property6.property8": "value3"
}
function addProp(obj, path, pathValue) {
var pathArray = path.split('.');
pathArray.reduce(function (acc, value, index) {
if (index === pathArray.length - 1) {
acc[value] = pathValue;
return acc;
} else if (acc[value]) {
if (typeof acc[value] === "object" && index !== pathArray.length - 1) {
return acc[value];
} else {
var child = {};
acc[value] = child;
return child;
}
} else {
var child = {};
acc[value] = child;
return child;
}
}, obj);
}
var keys = Object.keys(input);
var output = {};
keys.forEach(function (k) {
addProp(output, k, input[k]);
});
console.log(output);
I am making a helper library for myself. Firstly I am wrapping like this:
(function (Helper, window, document) {
var Helper = Helper || {};
var type = function () {
return {
isObject: function (value) {
return value !== null && Object.prototype.toString.call(value) === "[object Object]";
},
isNumber: function (value) {
return value !== null && Object.prototype.toString.call(value) === "[object Number]" && !isNaN(value);
},
isString: function (value) {
return value !== null && Object.prototype.toString.call(value) === "[object String]";
},
isArray: function (value) {
return value instanceof Array;
},
isFunction: function (value) {
return typeof value === "function";
},
isDate: function (value) {
return value instanceof(Date);
},
isRegExp: function (value) {
return value instanceof RegExp;
},
isBoolean: function (value) {
return value !== null && Object.prototype.toString.call(value) === "[object Boolean]";
},
isError: function (value) {
return value instanceof Error;
},
isNull: function (value) {
return value === null;
},
isUndefined: function (value) {
return typeof value === "undefined";
}
};
};
Helper.type = type;
})(Helper = window.helper || {}, window, document);
Now this works fine, but in order to reach the functions, I need to call a function like this Helper.type().isNumber(5) . How this should look so that it would return as object values, like this Helper.type.isNumber(5) ?
Instead of assigning function reference:
Helper.type = type;
assign a function return value instead:
Helper.type = type();
Just remove the function() wrapper from type:
(function (Helper, window, document) {
var Helper = Helper || {};
var type = {
isObject: function (value) {
return value !== null && Object.prototype.toString.call(value) === "[object Object]";
},
...
isUndefined: function (value) {
return typeof value === "undefined";
}
};
Helper.type = type;
})(Helper = window.helper || {}, window, document);
simply drop function type(){ and }; Helper.type = type; and replace return with Helper.type =
(function(Helper, window, document) {
Helper.type = {
isNumber: function () {...}
};
})(Helper = window.helper || {}, window, document);
I'd like to create a recursive function to parse json-like data as below. When key is xtype, a new class will be created. In particular, when xtype = gridpanel/treepanel, all the properties have to be its constructor argument, otherwise, properties will be added after class has been created.
My recursive function as below, I got an error 'too much recursion' at line 21 in ext-all.js.
Please take a look, how am I able to solve this problem?
codes in main program:
me.recursiveParser(null, data.json);
Ext.apply(me.root, me.parent);
me.desktopCfg = me.root;
recursiveParser function:
recursiveParser: function(nodeName, jsonData) {
var properties = {};
var isSpecial = false;
var isLeaf = true;
var parent, child, special;
//Base factor
for (var key in jsonData) {
var value = jsonData[key];
//To collect all the properties that is only initialized with '#'.
if (key.toString().indexOf("#") === 0) {
key = key.replace("#", "");
if(typeof(value) === "string"){
properties[key] = "'"+value+"'";
}else{
//Later, should have to deal with the empty value or array with no keys and only elements.
properties[key] = value;
}
if(key === "xtype"){
//To initialize the root
if(nodeName === null){
this.root = this.createNewObject(value, null);
}
if(value === "gridpanel" || value === "treepanel"){
isSpecial = true;
special = value;
}else{
child = this.createNewObject(value, null);
}
}
}else {
isLeaf = false;
}
}
if(isSpecial){
child = this.createNewObject(special, properties);
}
//To add the subnode and its properties to its parent object.
if (nodeName !== null && typeof(nodeName) === "string") {
if(child === null){
Ext.apply(parent, properties);
}else{
Ext.apply(parent, child);
}
}
if(isLeaf){
return;
}
for (var key in jsonData) {
var value = jsonData[key];
if (key.toString().indexOf("#") === 0) {
continue;
}else{
if(value === "[object Object]"){
for(var index in value){
this.recursiveParser(key, value[index]);
}
}else{
this.recursiveParser(key, value);
}
Ext.apply(this.root, parent);
}
}
}
createNewObject function:
createNewObject: function(objType, properties){
if(objType){
switch (objType){
case "gridpanel":
return new MyProg.base.GridPanel(properties);
break;
case "treepanel":
return new MyProg.base.TreePanel(properties);
break;
case "tabpanel":
return new MyProg.base.TabPanel();
break;
case "tab":
return new MyProg.base.Tabs();
break;
case "formpanel":
return new MyProg.base.Accordion();
break;
case "fieldset":
return new MyProg.base.FieldSet();
break;
case "textfield":
return new MyProg.base.Fields();
break;
case "panel":
return new MyProg.base.Accordion();
break;
default:
return new MyProg.base.Accordion();
};
};
}
data.json:
var data = {
"json": {
"#title": "BusinessIntelligence",
"#xtype": "tab",
"#layout": "accordion",
"items": [
{
"#title": "SalesReport",
"#ctitle": "SalesReport",
"#layout": "column",
"items": [
{}
]
},
{
"#title": "ContentPlayingReport",
"#ctitle": "ContentPlayingReport",
"#layout": "column",
"items": [
{}
]
},
{
"#title": "BusinessIntelligence",
"#ctitle": "BusinessIntelligence",
"#layout": "column",
"items": [
{}
]
}
]
}
}
I modified the recursion part, it looks more elegant now. All the xtype works just fine, except gridpanel, I've check DOM, everything is in there, but still got error message:
TypeError: c is undefined
...+g.extraBaseCls);delete g.autoScroll;if(!g.hasView){if(c.buffered&&!c.remoteSort...
ext-all.js (line 21, col 1184416)
I suspect it's an ExtJS bug. I'll try to find another way out.
recursion program:
recursiveParser: function (jsonData) {
var me = this;
var properties = {};
for ( var key in jsonData ){
var value = jsonData[key];
var items = (value.constructor === Array) ? [] : {};
if (value instanceof Object) {
if (isNaN(key)){
if (items.constructor === Array) {
for (var node in value){
items.push(me.recursiveParser(value[node]));
}
properties[key] = items;
} else {
properties[key] = me.recursiveParser(value);
}
} else {
return me.recursiveParser(value);
}
} else {
if (key.toString().indexOf('#') === 0){
key = key.replace('#', '');
properties[key] = value;
}
}
}
return properties;
}
I have an object that I need to check the properties to see if they are all strings and if so return true, but mine always returns true.
function StringsObj(x) {
for (var prop in x) {
if(typeof x[prop] === "string") {
return true;
}
else {
return false;
}
}
}
var student = {
name: "Judy",
class: "freshman",
age: 19,
honors: true
};
StringsObj(student)
It happens because after the first check you function returns true ("Judy" is of string type) and stops executing. You can do something like this :
function StringsObj(x) {
for (var prop in x) {
if(typeof x[prop] !== "string") {
return false;
}
}
return true;
}
JSFiddle