Recursive hasOwnProperty for objects - javascript

I have an object as:
const object = {};
object.property1 = 54;
object.property1.property1 = 60;
now I would like to achieve something like this:
if(object.hasOwnProperty('property1')){
//do something
}
else if(object.hasOwnProperty('property1').hasOwnProperty('property1')){
//do something
}
else{
//do something
}
But it fails at the else if part.
why can't we use hasOwnProperty recursively? what is the workaround? I am stuck at this for many hours now.
I have tried to use:
if(object.property1.property1){
//do something
}
but this gives me undefined
So how to get around this situation? Please help!

I would use a recursive function
const object = {};
object.property1 = {};
object.property1.property2 = 60;
if (hasOwnPropertyRecursive(object, 'property2')) {
console.log('yes')
} else {
console.log('no')
}
function hasOwnPropertyRecursive(obj, prop) {
if (typeof obj !== 'object' || obj === null) return false
if (obj.hasOwnProperty(prop)) return true
return Object.getOwnPropertyNames(obj).some(key => hasOwnPropertyRecursive(obj[key], prop))
}

#MisterJojo I am trying to assigning a value
so you may do that this way...
const
objA = {}
, objB = { propX: 'b' }
, objC = { propX: { propX: 'c' } }
;
setLastProp( objA, 'propX', 'x_A' );
setLastProp( objB, 'propX', 'x_B' );
setLastProp( objC, 'propX', 'x_C' );
console.log( 'objA ->', JSON.stringify( objA ));
console.log( 'objB ->', JSON.stringify( objB ));
console.log( 'objC ->', JSON.stringify( objC ));
function setLastProp ( obj, propName, value )
{
let next = obj, prev = obj, count = 0;
while (next.hasOwnProperty(propName))
{
prev = next;
next = next[propName];
};
prev[propName] = value;
}

Related

How to I find a certain element in an array and change it?

I need to create a function to find the element "Tolkn" and change it to "Tolkien".
changeName = (arr) => {
if (arr === "Tolkn") {
arr.push("Tolkien")
}
return arr;
}
Modify your function like this
let arr = ['test', 'test2', 'Tolkn', 'test3'];
let changeName = (inpArr) => {
let index = inpArr.findIndex(function(itm) {
return itm == 'Tolkn';
})
if (index === -1) {
return inpArr;
}
inpArr[index] = 'Tolkien';
return inpArr;
};
console.log(changeName(arr));
Another way with forEach
let arr = ['test', 'test2', 'Tolkn', 'test3'];
function changeName(arr) {
let output = [];
arr.forEach(function(itm) {
if (itm == "Tolkn") {
output.push("Tolkien")
} else {
output.push(itm);
}
});
return output;
}
console.log(changeName(arr));
Making a copy with correct values (now in an even more concise form thanks to #MattMorgan).
var arr = ["Tolkn","a","some","Tolkn"];
var changeName = arr.map.bind(arr, elem => elem === "Tolkn" ? "Tolkien" : elem);
arr = changeName(arr);
console.log(arr);
Or in place replacement:
var arr = ["Tolkn","a","some","Tolkn"];
var changeName = arr.forEach.bind(arr, (val, ind) => {arr[ind] = (val === "Tolkn" ? "Tolkien" : val)});
changeName(arr);
console.log(arr);
Short version if you don't mind a possible hidden "-1" property:
let changeName = arr => (arr[arr.indexOf("Tolkn")] = "Tolkien", arr)
console.log( changeName( ['Tolkn'] ) ) // ['Tolkien']
console.log( changeName( ['Talkn'] ) ) // ['Talkn']

List all possible paths using lodash

I would like to list all paths of object that lead to leafs
Example:
var obj = {
a:"1",
b:{
foo:"2",
bar:3
},
c:[0,1]
}
Result:
"a","b.foo","b.bar", "c[0]","c[1]"
I would like to find simple and readable solution, best using lodash.
Here is a solution that uses lodash in as many ways as I can think of:
function paths(obj, parentKey) {
var result;
if (_.isArray(obj)) {
var idx = 0;
result = _.flatMap(obj, function (obj) {
return paths(obj, (parentKey || '') + '[' + idx++ + ']');
});
}
else if (_.isPlainObject(obj)) {
result = _.flatMap(_.keys(obj), function (key) {
return _.map(paths(obj[key], key), function (subkey) {
return (parentKey ? parentKey + '.' : '') + subkey;
});
});
}
else {
result = [];
}
return _.concat(result, parentKey || []);
}
Edit: If you truly want just the leaves, just return result in the last line.
Doesn't use lodash, but here it is with recursion:
var getLeaves = function(tree) {
var leaves = [];
var walk = function(obj,path){
path = path || "";
for(var n in obj){
if (obj.hasOwnProperty(n)) {
if(typeof obj[n] === "object" || obj[n] instanceof Array) {
walk(obj[n],path + "." + n);
} else {
leaves.push(path + "." + n);
}
}
}
}
walk(tree,"tree");
return leaves;
}
Based on Nick answer, here is a TS / ES6 imports version of the same code
import {isArray,flatMap,map,keys,isPlainObject,concat} from "lodash";
// See https://stackoverflow.com/a/36490174/82609
export function paths(obj: any, parentKey?: string): string[] {
var result: string[];
if (isArray(obj)) {
var idx = 0;
result = flatMap(obj, function(obj: any) {
return paths(obj, (parentKey || '') + '[' + idx++ + ']');
});
} else if (isPlainObject(obj)) {
result = flatMap(keys(obj), function(key) {
return map(paths(obj[key], key), function(subkey) {
return (parentKey ? parentKey + '.' : '') + subkey;
});
});
} else {
result = [];
}
return concat(result, parentKey || []);
}
Feeding that object through this function should do it I think.
recursePaths: function(obj){
var result = [];
//get keys for both arrays and objects
var keys = _.map(obj, function(value, index, collection){
return index;
});
//Iterate over keys
for (var key in keys) {
//Get paths for sub objects
if (typeof obj[key] === 'object'){
var paths = allPaths(obj[key]);
for (var path in paths){
result.push(key + "." + path);
}
} else {
result.push(key);
}
}
return result;
}
Here is my function. It generates all possible paths with dot notation, assuming there are no property names containing spaces
function getAllPathes(dataObj) {
const reducer = (aggregator, val, key) => {
let paths = [key];
if(_.isObject(val)) {
paths = _.reduce(val, reducer, []);
paths = _.map(paths, path => key + '.' + path);
}
aggregator.push(...paths);
return aggregator;
};
const arrayIndexRegEx = /\.(\d+)/gi;
let paths = _.reduce(dataObj, reducer, []);
paths = _.map(paths, path => path.replace(arrayIndexRegEx, '[$1]'));
return paths;
}
Here's my solution. I only did it because I felt the other solutions used too much logic. Mine does not use lodash since I don't think it would add any value. It also doesn't make array keys look like [0].
const getAllPaths = (() => {
function iterate(path,current,[key,value]){
const currentPath = [...path,key];
if(typeof value === 'object' && value != null){
return [
...current,
...iterateObject(value,currentPath)
];
}
else {
return [
...current,
currentPath.join('.')
];
}
}
function iterateObject(obj,path = []){
return Object.entries(obj).reduce(
iterate.bind(null,path),
[]
);
}
return iterateObject;
})();
If you need one where the keys are indexed using [] then use this:
const getAllPaths = (() => {
function iterate(path,isArray,current,[key,value]){
const currentPath = [...path];
if(isArray){
currentPath.push(`${currentPath.pop()}[${key}]`);
}
else {
currentPath.push(key);
}
if(typeof value === 'object' && value != null){
return [
...current,
...iterateObject(value,currentPath)
];
}
else {
return [
...current,
currentPath.join('.')
];
}
}
function iterateObject(obj,path = []){
return Object.entries(obj).reduce(
iterate.bind(null,path,Array.isArray(obj)),
[]
);
}
return iterateObject;
})();
const allEntries = (o, prefix = '', out = []) => {
if (_.isObject(o) || _.isArray(o)) Object.entries(o).forEach(([k, v]) => allEntries(v, prefix === '' ? k : `${prefix}.${k}`, out));
else out.push([prefix, o]);
return out;
};
Array are returned as .0 or .1 that are compatible with _.get of lodash
const getAllPaths = (obj: object) => {
function rKeys(o: object, path?: string) {
if (typeof o !== "object") return path;
return Object.keys(o).map((key) =>
rKeys(o[key], path ? [path, key].join(".") : key)
);
}
return rKeys(obj).toString().split(",").filter(Boolean) as string[];
};
const getAllPaths = (obj) => {
function rKeys(o, path) {
if (typeof o !== "object") return path;
return Object.keys(o).map((key) =>
rKeys(o[key], path ? [path, key].join(".") : key)
);
}
return rKeys(obj).toString().split(",").filter(Boolean);
};
const test = {
a: {
b: {
c: 1
},
d: 2
},
e: 1
}
console.log(getAllPaths(test))

Create a JavaScript object from string

I'm trying to create a JavaScript function that creates an object using strings for structure and fills it from DOM data.
For example, the following strings could look like this:
some.example.here = "hello"
some.example.there = "hi"
other.example = "heyo"
Which should create this object:
{
some: {
example: {
here: "hello",
there: "hi"
},
other: {
example: "heyo
}
}
The data as said comes from DOM and is being load at the code segment labeled "read data into object". The data loads fine and the object structure is being setup fine as well, but the data is not being put into the data field.
Here's the code for the function:
function getDataFromElement(element) {
obj = {};
$(element)
.find("[data-value]")
.each(function() {
// create object node
valueObj = {};
currentValueObj = valueObj;
$.each($(this).attr("data-value").split("."), function(i, objpath) {
currentValueObj[objpath] = {};
currentValueObj = currentValueObj[objpath];
});
// read data into object
if($(this).is("[data-putvalue]") && $(this).attr("data-putvalue") != "html") {
currentValueObj = $(this).attr($(this).attr("data-putvalue"));
} else {
currentValueObj = $(this).html();
}
console.log(currentValueObj);
// combine with previous gathered data
obj = $.extend(true, {}, obj, valueObj);
});
return obj;
}
Does anyone know what to do?
I would do it like this:
var createObject = function(model, name, value) {
var nameParts = name.split("."),
currentObject = model;
for (var i in nameParts) {
var part = nameParts[i];
if (i == nameParts.length-1) {
currentObject[part] = value;
break;
}
if (typeof currentObject[part] == "undefined") {
currentObject[part] = {};
}
currentObject = currentObject[part];
}
};
And then use it like that:
var model = {};
createObject(model, "some.example.here", "hello");
createObject(model, "some.example.there", "hi");
createObject(model, "other.example", "heyo");
Probably this can suit you (adapted from another project of mine, adapt and use as needed):
NOTE the element's name is taken as key and value as the value
function fields2model( $elements, dataModel )
{
$elements.each(function( ){
var $el = $(this),
name = $el.attr('name'),
key, k, i, o, val
;
key = name;
val = $el.val() || '';
k = key.split('.'); o = dataModel;
while ( k.length )
{
i = k.shift( );
if ( k.length )
{
if ( !o.hasOwnProperty( i ) ) o[ i ] = /^\d+$/.test( k[0] ) ? [ ] : { };
o = o[ i ];
}
else
{
o[ i ] = val;
}
}
});
}
Example use:
<input name="some.example.here" value="hello" />
<input name="some.example.there" value="hi" />
var model = {};
fields2model($('input,textarea,select'), model);
The example elements above will give the below model:
model = {
some: {
example: {
here: "hello",
there: "hi"
}
};
Some functional implementation:
const value = 'hello';
'some.example.here'.split('.').reverse().reduce((reduction, segment, index) => {
const result = {};
if (index === 0) {
result[segment] = value;
} else {
result[segment] = reduction;
}
return result;
}, {})
#theFreedomBanana +1
Works for me
const magicFunction = (string, value) =>
string
.split('.')
.reverse()
.reduce((acc, cur, index) => ({ [cur]: index === 0 ? value : acc }), {});

Deep changing values in a JavaScript object

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

callback function in loop to fire only once

I have a loop, iterating over an object that runs a function with a callback. The problem is I want the callback to only run after all the items have gone through the loop... not each and every time.
$scope.myArray = [];
Object.keys($scope.obj).forEach(function(key) {
var curNode = $scope.obj[key];
if ( curNode ) {
if ( x = 3 ) {
$scope.myArray.push(curNode);
myFunction({
//do something before the callback,
}, myCalback( myArray ) );
}
}
});
$scope.myCallback = function ( arry ) {
//do something ONCE with arry
}
There is also a bug x = 3
'=' is assignment '==' is equality without type comparison.
$scope.myArray = [];
Object.keys($scope.obj).forEach(function(key) {
var curNode = $scope.obj[key];
if ( curNode ) {
if ( x == 3 ) {
$scope.myArray.push(curNode);
myFunction({
//do something before the callback,
});
}
}
});
$scope.myCallback = function ( arry ) {
//do something ONCE with arry
}
$scope.myCalback( $scope.myArray ) ;
Edit:
This won't fire the callback unless there was a matched item and only fires after finished iterating:
$scope.myArray = [];
var run = false;
Object.keys($scope.obj).forEach(function(key) {
var curNode = $scope.obj[key];
if ( curNode ) {
if ( x == 3 ) {
$scope.myArray.push(curNode);
myFunction({
//do something before the callback,
}, function() {run = true} );
}
}
});
$scope.myCallback = function ( arry ) {
//do something ONCE with arry
}
if (run) $scope.myCallback($scope.myArray);

Categories

Resources