I have a data structure like the following:
obj
name
parent
name
parent
name
parent
I'm trying to do a recursive function to get the name element for as long as there is a parent element. I've come up with the following code, but it doesn't work because it converts the result to a string (and not an object). Can someone give me some advice on how to best achieve this. The nesting of parent elements is varied, not fixed (even though above I only specified 3 layers). It can range from 1 to 100.
My Desired Result
My end goal is to extract all the 'name' elements from every 'parent' down the hierarchy (and push them to an array).
function getElem(obj){
var result = '';
var parent = '.parent';
var temp = '';
if(!obj.parent){
return obj.name
}
else {
//structure is obj.parent.parent.parent...name
temp += parent;
result = obj + temp + '.name';
console.log(result);
getElem(result);
}
}
getElem(e.data);
function getElem(obj, arr) {
arr = arr || [];
if (obj.name) {
arr.push(obj.name);
}
if (obj.parent) {
return (getElem(obj.parent, arr));
}
else {
return (arr);
}
}
JSFiddle
Assuming that your data structure looks like this
var obj = {
name: 'abc',
parent: {
name: 'def',
parent: {
name: 'ghi',
parent: {
name: 'jkl'
}
}
}
};
function for loop that walks recursively for "parent" key by assigning "parent" key's value in obj and breaks the loop when the key "parent" is not available...
function get_names(obj, key) {
var names = [];
if(key == "undefined") {
key = "parent"; // default value of key
}
do {
if(typeof obj === "undefined") {
break; // breaks the loop when obj/parent is undefined
}
names.push(obj.name); // pushes all the elements named as 'name'
} while(obj = obj.parent);
return names;
}
Logs the array in console
console.log(get_names(obj));
I don't think you need recursion.
function getElem(obj) {
var names = [obj.name];
while (obj.parent) {
obj = obj.parent;
names.push(obj.name);
}
return names;
}
Related
I have been using a version of the function below to create and/or add values to nested objects:
function assign(obj, keyPath, value) {
const lastKeyIndex = keyPath.length-1;
for (let i = 0; i < lastKeyIndex; ++ i) {
const key = keyPath[i];
if (!(key in obj)){
obj[key] = {}
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
(Posted in 2011 by kennytm, slightly modified above:
Javascript: how to dynamically create nested objects using object names given by an array).
Example usage to specify a value in which the keys represent the (0) database
name, (1) table name, (3) id value, and (4) column name:
let obj = {}
assign(obj, ['farm', 'products', '25', 'product_name'], 'lettuce');
console.log(JSON.stringify(obj));
/* (reformatted)
{
"farm": {
"products": {
"25": {
"product_name":"lettuce"
}
}
}
}
*/
We can add a second value for the same row:
assign(obj, ['farm', 'products', '25', 'product_unit'], 'head');
console.log(JSON.stringify(obj));
/* (reformatted)
{
"farm": {
"products": {
"25": {
"product_name":"lettuce",
"product_unit":"head"
}
}
}
}
*/
Or additional values from different rows, tables, and databases:
assign(obj, ['farm', 'equipment', '17', 'equipment_name'], 'tractor');
console.log(JSON.stringify(obj));
/* (reformatted)
{
"farm": {
"products": {
"25": {
"product_name": "lettuce",
"product_unit": "head"
}
},
"equipment": {
"17": {
"equipment_name": "tractor"
}
}
}
}
*/
The function works perfectly but I can't figure out how it manages to aggregate the key path. It would appear to simply create or replace an existing object with an object consisting of only the last key and the value. In fact, if I execute the same statements not inside a function and without using a loop, the statements do exactly that.
(Starting with assignment of the first value to an empty object):
let obj = {}
let key;
// first iteration of the function's loop
key = 'farm';
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key];
// second iteration
key = 'products';
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key];
// third iteration
key = '25';
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key];
// final line from the function
obj['product name'] = 'lettuce';
console.log(JSON.stringify(obj));
// {"product name":"lettuce"}
As you can see, the object is not nested but simply replaced at each step.
What magic makes the function work differently?
The difference is that you didn't save a variable with a reference to the original object. When you reassign obj, you no longer have a variable containing the parent.
When using the function, that variable is in the function caller. Reassigning the local variable obj has no effect on the caller's variable.
To emulate this in your manual steps, change the beginning to:
const original = {};
let obj = original;
This is analogous to the way the function parameter is passed.
Then at the very end, do
console.log(JSON.stringify(original));
and you should see the whole object with all the nested properties added.
I have a JS Object that may look like one of the following:
// Example #1:
var data = {
product: {
value1: 'v1',
value2: 2
}
}
// Example #2:
var data = {
order: {
value1: 1
}
}
// Example #3:
var data = {
value1: 1
}
What I'd like to achieve:
var inputName = 'product[value1]';
var data = {
product: {
value1: 'v1',
value2: 2
}
}
var value = something(data, inputName); // should return v1
inputName and data can change, I can have any of the above data Objects with an inputName like 'product[value2]', 'order[value1]', 'value1'.
My guess is to use regex and get both attribute names. Is the a better way?
you can use underscore js _.each to iterate over the object like
_.each(data ,function(product){
console.log(product.value);
});
see the link: http://underscorejs.org/#each
you can also use for each loop.
Also you can perform filter like below:
_.filter(data, function(product){
return product.value;
});
One other way is to create a dictionary that directly caters to your search.
You can flatten your multi-level key value pair to create a dictionary that you can use readily. The below flatten function (taken from here) creates a dictionary like:
{
"product.value1": "v1",
"product.value2": 2
}
You can then just query it using dictionary["product.value1"]
This flatten function can be altered to format the keys like product[value1] if you wish so.
var data = {
product: {
value1: 'v1',
value2: 2
}
}
var myDictionary = flatten(data);
console.log(myDictionary);
console.log(myDictionary["product.value1"]);
console.log(myDictionary["product.something else"]);
function flatten(obj, opt_out, opt_paths) {
var out = opt_out || {};
var paths = opt_paths || [];
return Object.getOwnPropertyNames(obj).reduce(function(out, key) {
paths.push(key);
if (typeof obj[key] === 'object') {
flatten(obj[key], out, paths);
} else {
out[paths.join('.')] = obj[key];
}
paths.pop();
return out;
}, out)
}
If you know possible attribute names, then I would define an array with possible attribute names, then iterate over them checking if there is a field with this name
const names = [
'product',
'order'
];
function findValue(data){
if(data.value1) return data.value1;
for(let name in names){
if(data[name].value1) return data[name].value1;
}
}
Explanation
If you want to give your function a string like 'product[value1]' as argument you need to get all attribute values that you need to query for your result value. I did it with query.replace(/(\[)|(\])/g, ' ')split(' '). The returning array you need to check for empty strings and remove them. I did it with filter.
After that you can simply use reduce on the returned array to get on each iteration the new value. In the last iteration you have your result.
Code
function getDataValue(obj, query) {
var attributes = getAttributeNames(query)
return attributes.reduce(function(value, current) {
return value[current]
}, obj)
}
function getAttributeNames(query) {
return query.replace(/(\[)|(\])/g, ' ')
.split(' ')
.filter(function(string) {
return string.length > 0
})
}
Example
var dataOne = {
product: {
value1: 'v1',
value2: 2
}
}
var dataTwo = {
product: {
subProduct: {
value1: 'v2'
}
}
}
console.log(getDataValue(dataOne, 'product[value1]'))
console.log(getDataValue(dataTwo, 'product[subProduct][value1]'))
function getDataValue(obj, query) {
var attributes = getAttributeNames(query)
return attributes.reduce(function(value, current) {
return value[current]
}, obj)
}
function getAttributeNames(query) {
return query.replace(/(\[)|(\])/g, ' ')
.split(' ')
.filter(function(string) {
return string.length > 0
})
}
I'm attempting to "loop" through an object and grab the name if a certain test passes.
For instance
var x = {
'first':[
'ab',
'abc',
],
'second':[
'sia',
'sss'
],
'third':[
'jp',
'jh'
]
};
As you can see, I have an object containing arrays.
Let's say that I am given the value "sss" .... I am trying to figure out how to get Javascript to output "second" when given the appropriate value.
The same goes for the following scenarios
third for jp
third for jh
first for abc
and so on.
One possibility (where x is your object, and y is your desired array value):
function foo(y) {
for (key in x) {
if (x[key].indexOf(y) > -1) {
return key;
}
}
}
This will find the first key that has the value you are searching for in its array
function myFunction(searchValue, myObject){
for(var i in myObject){
if(myObject.hasOwnProperty(i) && myObject[i].indexOf(searchValue) != -1){
return i;
}
}
}
You know what. No sooner I click Submit Question, did I realized how to do it.
var x = {
'first':[
'ab','abc',
],
'second':[
'sia','sss'
],
'third':[
'jp','jh',
]
};
for(var arr in x) {
var theArr = x[arr];
if(theArr.indexOf("sss") !== -1) console.log(arr) // returns "second"
}
And if you want to exclude properties in the prototype chain use the hasOwnProperty function:
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
console.log('property: ' + property + ' value: ' + obj[property]);
}
}
The in operator will traverse the prototype chain and return all properties in the object.
var x = {
'first':[
'ab',
'abc',
],
'second':[
'sia',
'sss'
],
'third':[
'jp',
'jh'
]
};
var result = Object.keys(x).filter(function(key) {
return x[key].indexOf('sss') > -1;
});
Afterwards, result will be an array containing all keys that contain the value sss, so it could be 0, 1 or more, if duplicates exist.
In this code i intend to find all the nested object literals in a parent object.One of my nested object has an array called places as its own property.I have a recursive function called getObjNum which goes through every property and finds nested object literals inside parent object.
In my Parent object there are three nested object literals.One of them has an array as property.So container array which is supposed to store all the found nested object literals must have 3 indexes.But array places also included as fourth index to container array.I found
typeof(array)=='object' returns true;
arr instanceof Object also returns true;
How can i prevent the place array to be inserted to container array along with other object literals??
var getValue;
var parentObj = {
parentProp: 10,
childObj: {
childProp: 20,
grandChildObj: {
y: {
z:'lol',
places:['newyork','canada','dhaka']
}
}
}
}
var arr=[];
var container=[];
var count=0;
function getObjNum(obj){
var prop=Object.getOwnPropertyNames(obj);
for(i=0;i<prop.length;i++){
if(typeof(obj[prop[i]])=='object'){
container.push(obj[prop[i]]);
count++;
getObjNum(obj[prop[i]]);
}
}
}
getObjNum(parentObj);
console.log(container);
As pointed out in the comments, you can use instanceof Array to check if an object is an array or not, like so:
function getChildObjects(obj, returnArr) {
return Object.keys(obj).reduce(function (arr, key) {
var prop = obj[key];
if (typeof prop === 'object' && !(prop instanceof Array)) {
arr.push(prop);
arr = getChildObjects(prop, arr);
}
return arr;
}, returnArr || []);
}
var parentObj = {
parentProp: 10,
childObj: {
childProp: 20,
grandChildObj: {
y: {
z:'lol',
places:['newyork','canada','dhaka']
}
}
}
};
var childs = getChildObjects(parentObj);
console.log(childs, childs.length);
Consider:
var object = {
foo: {},
bar: {},
baz: {}
}
How would I do this:
var first = object[0];
console.log(first);
Obviously, that doesn’t work because the first index is named foo,
not 0.
console.log(object['foo']);
works, but I don’t know it’s named foo. It could be named anything. I just want the first.
Just for fun this works in JS 1.8.5
var obj = {a: 1, b: 2, c: 3};
Object.keys(obj)[0]; // "a"
This matches the same order that you would see doing
for (o in obj) { ... }
If you want something concise try:
for (first in obj) break;
alert(first);
wrapped as a function:
function first(obj) {
for (var a in obj) return a;
}
they're not really ordered, but you can do:
var first;
for (var i in obj) {
if (obj.hasOwnProperty(i) && typeof(i) !== 'function') {
first = obj[i];
break;
}
}
the .hasOwnProperty() is important to ignore prototyped objects.
This will not give you the first one as javascript objects are unordered, however this is fine in some cases.
myObject[Object.keys(myObject)[0]]
If the order of the objects is significant, you should revise your JSON schema to store the objects in an array:
[
{"name":"foo", ...},
{"name":"bar", ...},
{"name":"baz", ...}
]
or maybe:
[
["foo", {}],
["bar", {}],
["baz", {}]
]
As Ben Alpert points out, properties of Javascript objects are unordered, and your code is broken if you expect them to enumerate in the same order that they are specified in the object literal—there is no "first" property.
for first key of object you can use
console.log(Object.keys(object)[0]);//print key's name
for value
console.log(object[Object.keys(object)[0]]);//print key's value
There is no way to get the first element, seeing as "hashes" (objects) in JavaScript have unordered properties. Your best bet is to store the keys in an array:
var keys = ["foo", "bar", "baz"];
Then use that to get the proper value:
object[keys[0]]
ES6
const [first] = Object.keys(obj)
Using underscore you can use _.pairs to get the first object entry as a key value pair as follows:
_.pairs(obj)[0]
Then the key would be available with a further [0] subscript, the value with [1]
I had the same problem yesterday. I solved it like this:
var obj = {
foo:{},
bar:{},
baz:{}
},
first = null,
key = null;
for (var key in obj) {
first = obj[key];
if(typeof(first) !== 'function') {
break;
}
}
// first is the first enumerated property, and key it's corresponding key.
Not the most elegant solution, and I am pretty sure that it may yield different results in different browsers (i.e. the specs says that enumeration is not required to enumerate the properties in the same order as they were defined). However, I only had a single property in my object so that was a non-issue. I just needed the first key.
You could do something like this:
var object = {
foo:{a:'first'},
bar:{},
baz:{}
}
function getAttributeByIndex(obj, index){
var i = 0;
for (var attr in obj){
if (index === i){
return obj[attr];
}
i++;
}
return null;
}
var first = getAttributeByIndex(object, 0); // returns the value of the
// first (0 index) attribute
// of the object ( {a:'first'} )
To get the first key of your object
const myObject = {
'foo1': { name: 'myNam1' },
'foo2': { name: 'myNam2' }
}
const result = Object.keys(myObject)[0];
// result will return 'foo1'
Based on CMS answer. I don't get the value directly, instead I take the key at its index and use this to get the value:
Object.keyAt = function(obj, index) {
var i = 0;
for (var key in obj) {
if ((index || 0) === i++) return key;
}
};
var obj = {
foo: '1st',
bar: '2nd',
baz: '3rd'
};
var key = Object.keyAt(obj, 1);
var val = obj[key];
console.log(key); // => 'bar'
console.log(val); // => '2nd'
My solution:
Object.prototype.__index = function(index)
{
var i = -1;
for (var key in this)
{
if (this.hasOwnProperty(key) && typeof(this[key])!=='function')
++i;
if (i >= index)
return this[key];
}
return null;
}
aObj = {'jack':3, 'peter':4, '5':'col', 'kk':function(){alert('hell');}, 'till':'ding'};
alert(aObj.__index(4));