How to read objects with sub-objects - javascript

I have an object with several sub-objects and I would like to retrieve all elements.
When running the following code, I only retrieve part of the elements till the 'age'
var output = '';
var main_table = {
animal: 'dog',
color:'black',
age: {
year:2016,
month:11,
day:1
},
race:'sheepdog',
parents: {
father:'Dad',
mother:'Mom'
}
};
function test(main_table){
table=main_table;
for (var name in table) {
if (table.hasOwnProperty(name)) {
if (table[name]=="[object Object]") {
test(table[name]);
}
else {
output+=(name+' : '+table[name]+' ');
}
}
}
alert (output);
}
test(main_table)
Some help on it will be highly appreciated.

You had created an implicit global variable with this line:
table=main_table;
by missing out the var.
I have also refactored a little bit to return the output at each recursive stage, and alert at the end.
var main_table = {
animal: 'dog',
color:'black',
age:
{
year:2016,
month:11,
day:1
},
race:'sheepdog',
parents:
{
father:'Dad',
mother:'Mom'}
};
function test(main_table){
var table=main_table;
var output = '';
for (var name in table)
{
if (table.hasOwnProperty(name))
{
console.log(name, typeof table[name])
if (typeof table[name]== "object")
{
output+=test(table[name]);
}
else
{
output+=(name+' : '+table[name]+' ');
}
}
}
return output;
}
alert(test(main_table))

I suggest to use an iterative, over the keys and recursive, over the children, approach, with a proper check
if (object[key] !== null && typeof object[key] === 'object') { //...
for iterable objects.
Methods used:
Object.keys returns an array with own properties of the object
Array#forEach for iterating the array
function getElements(object) {
var result = [];
Object.keys(object).forEach(function (key) {
if (object[key] !== null && typeof object[key] === 'object') {
result = result.concat(getElements(object[key]));
return;
}
result.push([key, object[key]]);
});
return result;
}
var main_table = { animal: 'dog', color: 'black', age: { year: 2016, month: 11, day: 1 }, race: 'sheepdog', parents: { father: 'Dad', mother: 'Mom' } };
console.log(getElements(main_table));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Hi you set a wrong scope to your function because of this line table=main_table;
this code will work i suppose :
var output = '';
var main_table = {
animal: 'dog',
color:'black',
age:
{year:2016,month:11,day:1},
race:'sheepdog',
parents:
{father:'Dad',
mother:'Mom'}
};
function test(table){
for (var name in table)
{
if (table.hasOwnProperty(name))
{
if (table[name]=="[object Object]")
{
test(table[name]);
}
else
{
output+=(name+' : '+table[name]+' ');
}
}
}
alert(output);
}
test(main_table);

Related

Convert object to array of prorperties

I need to convert object:
{
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
}
to array:
[{
key: "name",
propertyValue: "Test Name",
},
{
key: "middleName",
propertyValue: null,
},
{
key: "university.isGraduated",
propertyValue: true,
},
{
key: "university.speciality",
propertyValue: "Computer Science",
},
{
key: "university.country.code",
propertyValue: "PL"
}];
I wrote algorithm, but it's pretty dummy, how can I improve it? Important, if object has nested object than I need to write nested object via dot (e.g university.contry: "value")
let arr = [];
Object.keys(parsedObj).map((key) => {
if (parsedObj[key] instanceof Object) {
Object.keys(parsedObj[key]).map((keyNested) => {
if (parsedObj[key][keyNested] instanceof Object) {
Object.keys(parsedObj[key][keyNested]).map((keyNestedNested) => {
arr.push({ 'key': key + '.' + keyNested + '.' + keyNestedNested, 'propertyValue': parsedObj[key][keyNested][keyNestedNested] })
})
} else {
arr.push({ 'key': key + '.' + keyNested, 'propertyValue': parsedObj[key][keyNested] })
}
})
} else {
arr.push({ 'key': key, 'propertyValue': parsedObj[key] })
}
});
Thanks
A recursive function implementation.
I have considered that your object consist of only string and object as the values. If you have more kind of data types as your values, you may have to develop on top of this function.
const myObj = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
}
const myArr = [];
function convertObjectToArray(obj, keyPrepender) {
Object.entries(obj).forEach(([key, propertyValue]) => {
if (typeof propertyValue === "object" && propertyValue) {
const updatedKey = keyPrepender ? `${keyPrepender}.${key}` : key;
convertObjectToArray(propertyValue, updatedKey)
} else {
myArr.push({
key: keyPrepender ? `${keyPrepender}.${key}` : key,
propertyValue
})
}
})
}
convertObjectToArray(myObj);
console.log(myArr);
I'd probably take a recursive approach, and I'd probably avoid unnecessary intermediary arrays (though unless the original object is massive, it doesn't matter). For instance (see comments):
function convert(obj, target = [], prefix = "") {
// Loop through the object keys
for (const key in obj) {
// Only handle "own" properties
if (Object.hasOwn(obj, key)) {
const value = obj[key];
// Get the full key for this property, including prefix
const fullKey = prefix ? prefix + "." + key : key;
if (value && typeof value === "object") {
// It's an object...
if (Array.isArray(value)) {
throw new Error(`Arrays are not valid`);
} else {
// ...recurse, providing the key as the prefix
convert(value, target, fullKey);
}
} else {
// Not an object, push it to the array
target.push({key: fullKey, propertyValue: value});
}
}
}
// Return the result
return target;
}
Live Example:
const original = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
};
function convert(obj, target = [], prefix = "") {
// Loop through the object keys
for (const key in obj) {
// Only handle "own" properties
if (Object.hasOwn(obj, key)) {
const value = obj[key];
// Get the full key for this property, including prefix
const fullKey = prefix ? prefix + "." + key : key;
if (value && typeof value === "object") {
// It's an object...
if (Array.isArray(value)) {
throw new Error(`Arrays are not valid`);
} else {
// ...recurse, providing the key as the prefix
convert(value, target, fullKey);
}
} else {
// Not an object, push it to the array
target.push({key: fullKey, propertyValue: value});
}
}
}
// Return the result
return target;
}
const result = convert(original, []);
console.log(result);
.as-console-wrapper {
max-height: 100% !important;
}
Note that I've assumed the order of the array entries isn't significant. The output you said you wanted is at odds with the standard order of JavaScript object properties (yes, they have an order now; no, it's not something I suggest relying on 😀). I've gone ahead and not worried about the order within an object.
This is the most bulletproof I could do :D, without needing a global variable, you just take it, and us it wherever you want!
const test = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
};
function toPropertiesByPath(inputObj) {
let arr = [];
let initialObj = {};
const getKeys = (obj, parentK='') => {
initialObj = arr.length === 0 ? obj: initialObj;
const entries = Object.entries(obj);
for(let i=0; i<entries.length; i++) {
const key = entries[i][0];
const val = entries[i][1];
const isRootElement = initialObj.hasOwnProperty(key);
parentK = isRootElement ? key: parentK+'.'+key;
if(typeof val === 'object' && val!==null && !Array.isArray(val)){
getKeys(val, parentK);
} else {
arr.push({ key: parentK, property: val });
}
}
};
getKeys(inputObj);
return arr;
}
console.log(toPropertiesByPath(test));
I wrote a small version using recursive function and another for validation is an object.
let values = {
middleName: null,
name: "Test Name",
university: {
country: {
code: "PL"
},
isGraduated: true,
speciality: "Computer Science"
}
}
function isObject(obj) {
return obj != null && obj.constructor.name === "Object"
}
function getValues(values) {
let arrValues = Object.keys(values).map(
v => {
return { key: v, value: isObject(values[v]) ? getValues(values[v]) : values[v] };
});
console.log(arrValues);
}
getValues(values);

how to not print multiple times?

I had a task to print out all the fields of a given object
and i solved it like this:
function print(jsonObject) {
if (typeof(jsonObject) === 'object') {
for (var prop in Object.keys(jsonObject)) {
if (typeof(jsonObject[prop]) === 'object') {
print(jsonObject[prop]);
}else{
console.log(prop + ':' + jsonObject[prop]);
}
}
}
}
But what do I do if i had an object refrenced to one another?
like so:
let one = {
name: 'name',
age: 21
}
let two = {
name: {
firstName: 'name',
lastName: 'lastname'
}
age: 22,
}
two.ref = one
one.ref = two;
What is the stop condition?
thanks
Simple naive reference lookup, that checks if the same object has been traversed using a WeakSet().
Will have false positives on property values that reference the same object.
eg: one.ref1=one.ref
printer(one)
function printer(jsonObject) {
const seen = new WeakSet();
print(jsonObject)
function print(jsonObject) {
if (typeof(jsonObject) === 'object') {
if(jsonObject) seen.add(jsonObject);
for (var prop of Object.keys(jsonObject)) {
if (typeof(jsonObject[prop]) === 'object') {
if(jsonObject[prop]!==null) {
if(seen.has(jsonObject[prop])) {
console.log(`circular ref at key ${prop}:`,jsonObject[prop]);
return;
}
else
print(jsonObject[prop]);
seen.add(jsonObject[prop]);
}
}else{
console.log(prop + ':' + jsonObject[prop]);
}
}
}
}
}
<script>
let one = {
name: 'name',
age: 21
}
let two = {
name: {
firstName: 'name',
lastName: 'lastname'
},
age: 22,
}
two.ref = one;
one.ref = two;
</script>

how to use .include() method to check the value which is in a json inside array

I want to compare the value of a particular key in my JSON array with new value to check whether the value exists or not.
For example, I have an array:
[
{ name: abc, num: 121212 },
{ name: bcd, num: 21212 },
{ name: def, num: 111222 }
]
Now a new value comes which I want to check. Does that name already exist? If it does, then I only want to update the number and if not then I want to push the object in the array.
Here is my code:
if ((Dnum.num).includes(number)) {
console.log("inside if");
console.log(Dnum.indexOf(number));
} else {
Dnum.push({num:number,
lat:lat,
lng:lng,
name:name
});
}
Well, your problem (if I understand correctly) is that you want to use includes() but what you actually want to accomplish doesn't correspond to what the method does. You want to find if there's an object with a certain name in your array already, not if it contains a known element. Something like this:
var data = [{name: 'abc', num: 121212}, {name: 'bcd', num: 21212}, {name: 'def', num: 111222}];
function addOrUpdate(newElement, data) {
var i;
for (i = 0; i < data.length; i++) {
if (data[i].name == newElement.name) {
data[i] = newElement;
return;
}
}
data.push(newElement);
}
addOrUpdate({name: 'bcd', num: 131313}, data);
console.log(data);
addOrUpdate({name: 'new', num: 131313}, data);
console.log(data);
Problem:
Actually .includes() and .indexOf() methods won't work with objects, they should be used with an array of strings or Numbers as they use strict equality to compare the elements and objects can't be compared this way, so you need to implement this logic by yourself.
Solution:
You need to check if an object matching the searched name already exists in the array, update the num value of this object, otherwise if no object matches the searched name, push the new object to the array:
if (arr.some(function(obj) {
return obj.name === searchedVal.name;
})) {
arr.forEach(function(el, index) {
if (el.name === searchedVal.name) {
el.num += searchedVal.num;
found = true;
}
});
} else {
arr.push(searchedVal);
}
Demo:
var arr = [{
name: "abc",
num: 121212
}, {
name: "bcd",
num: 21212
}, {
name: "def",
num: 111222
}];
var searchedVal = {
name: "abc",
num: 5
};
if (arr.some(function(obj) {
return obj.name === searchedVal.name;
})) {
arr.forEach(function(el, index) {
if (el.name === searchedVal.name) {
el.num += searchedVal.num;
found = true;
}
});
} else {
arr.push(searchedVal);
}
console.log(arr);
If you don't want to use .some() method, you can do it this way:
var searchedVal = {
name: "abc",
num: 5
};
var found = false;
arr.forEach(function(el, index) {
if (el.name === searchedVal.name) {
el.num+= searchedVal.num;
found = true;
}
});
if (!found) {
arr.push(searchedVal);
}
Use Array.prototype.find():
var res = Dnum.find(function (item) {
return item.num === number;
});
if (res) {
console.log("inside if");
console.log(res);
res.num = number;
} else {
Dnum.push({
num:number,
lat:lat,
lng:lng,
name:name
});
}

Filter Array based on another array

I have a list with that contains a list of objects. Each object has 4 properties on it. There is a checkbox list with the unique values of two of the properties, this helps build my filter array.
the Filter might end up looking like this:
[
{
prop: 'username',
val: ['max', 'sam']
},
{
prop: 'color',
val: ['blue', 'green']
}
]
The list of objects would look something like this:
[
{
username: 'sam',
color: 'blue'
},
{
username: 'jimmy',
color: 'blue'
},
{
username: 'sam',
color: 'black'
},
{
username: 'max',
color: 'green'
},
{
username: 'max',
color: 'blue'
}
]
The Desired Result
[
{
username: 'sam',
color: 'blue'
},
{
username: 'max',
color: 'green'
},
{
username: 'max',
color: 'blue'
}
]
I feel like I'm going down a never ending forEach rabbit hole. I'm guessing I need some sort of recursion. Currently here is what I have:
var temporary = scope.transactions;
function getFilteredTransactions() {
var filter = deviceFilterService.get();
if (filter.length > 0) {
var temp2 = [];
angular.forEach(filter, function (fil) {
//object
angular.forEach(fil.val, function (filterValue) {
//list on each object
angular.forEach(temporary, function (transaction) {
if (transaction[fil.prop] === filterValue) {
if (temp2.indexOf(transaction) === -1) {
temp2.push(transaction);
}
}
});
temporary = temp2;
});
});
$log.debug(temporary);
scope.transactions = temporary;
} else {
initialize();
}
}
This is starting to work, the second time it goes through the property for color it ends up just wanting to add the exact same transaction to the temp2 array. There has to be a better way to set this up, possibly through recursion.
If you convert the format of the first list to a dictionary, i think if should get easier.
var dict = {};
angular.forEach(source1, function(ob){
dict[ob.prop] = ob.val;
});
function getFiltered(ob){
for(var prop in ob){
if(dict[prop] && dict[prop].indexOf(ob[prop]) === -1){
return false;
}
}
return true;
};
and just call it as:
var temporary = scope.transactions.filter(getFiltered);
Demo
Basically the first part converts:
[
{
prop: 'username',
val: ['max', 'sam']
},
{
prop: 'color',
val: ['blue', 'green']
}
];
to:
{
username:['max', 'sam'],
color:['blue', 'green']
}
so that it makes the look up much easier.
You might want to change the variable names here for clarity, but this will do what you're asking for:
var values = {};
angular.forEach(startingData, function(rawData) {
angular.forEach(rawData, function(value, key) {
if (angular.isUndefined(values[key])) {
values[key] = [];
}
if (values[key].indexOf(value) === -1) {
values[key].push(value);
}
})
});
var result = [];
angular.forEach(values, function(value, key) {
result.push({prop: key, val: value})
});
You can simply iterate each key of the data the needs filtering, find the appropriate filter per that key, and check the value against the filter values:
$scope.transactions = $scope.transactions.filter(isItemValidFilter);
function isItemValidFilter(item) {
var filters = deviceFilterService.get();
//For each property in the data, get the correct filter from the list of filters
var totalConditions = Object.keys(item).length;
var correctConditions = 0;
for (var filterKey in item) {
var correctFilters = filters.filter(function(dataFilter) {
return dataFilter.prop == filterKey
});
if (correctFilters.length) {
//Ill assume only 1 filter, so just use the 0 index
var correctFilter = correctFilters[0];
var conditions = correctFilter.val;
if (conditions && conditions.length) {
//check the values!
if (conditions.indexOf(item[filterKey]) > -1) {
correctConditions++;
}
}
}
}
return correctConditions === totalConditions;
}
Demo: http://jsfiddle.net/Lz32hka5/1/
Try:
var temp2 = [], matched;
angular.forEach(temporary, function(item){
matched = true;
angular.forEach(Object.keys(item), function(key){
angular.forEach(filter, function(filter){
filter.prop == key && filter.val.indexOf(item[key]) == -1 && (matched = false);
});
});
matched && temp2.push(item);
});
console.log(temp2)
temporary is the list of objects, filter: your filters
Demo: http://jsfiddle.net/wZVanG/7wnae850/

Get string representation of JavaScript object

I have a object like so:
$scope.query = {
filter: {
column: {
productName: 'Some Product',
price: 29.95,
...
},
table: {
productType: 'GM',
categoryId: 1,
...
}
}
};
How do I get a string that represents the whole object in dot notation? e.g.
query.filter.table.productType
To clarify, I am using this string value as a key to store a key/value pair in localStorage.
I am using angular to $wacth each property on the object for a change. Since you can't watch an object and know which property changed with watching all, I need to get creative and store each property in a key/value pair.
You can do it recursively, and produces "key" in an array.
var obj = {
query: {
filter: {
table: {
productType: 'GM'
}
}
}
};
var stringify = function (e) {
var rs = [];
for (var k in e) {
if (e.hasOwnProperty(k)) {
if (typeof e[k] == 'object') {
var l = stringify(e[k]);
for (var i = 0; i < l.length; i++) {
rs.push(k + '.' + l[i]);
}
} else {
rs.push(k);
}
}
}
return rs;
}
console.log(stringify(obj));
outputs:
["query.filter.table.productType"]
fiddle
Demo
Before Ques Edit
var $scope = {
query: {
filter: {
table: {
productType: 'GM'
}
}
}
};
var k = JSON.stringify($scope)
//output "{"query":{"filter":{"table":{"productType":"GM"}}}}"
k.match(/\w+(?=\"\:)/g).join('.')
//output"query.filter.table.productType"
Edit
Updated Demo
If OP has no issue with the position of child elements
var $scope = {}
$scope.query = {
filter: {
column: {
productName: 'Some Product',
price: 29.95
},
table: {
productType: 'GM',
categoryId: 1,
}
}
};
k=JSON.stringify($scope)
{"query":{"filter":{"column":{"productName":"Some Product","price":29.95},"table":{"productType":"GM","categoryId":1}}}}
k.match(/\w+(?=\"\:)/g).join('.')
"query.filter.column.productName.price.table.productType.categoryId"
By iterating the properties into an array recursively you could create a hierarchical structure that represents the data in the object. From here you could parse the results out as you wish.
var scope = {
query: {
filter: {
column: {
productName: 'Some Product',
price: 29.95
},
table: {
productType: 'GM',
categoryId: 1
}
}
}
};
function buildProps(subject) {
var result = [];
for (var key in subject) {
if (subject.hasOwnProperty(key)) {
if (typeof subject[key] == "object") {
result.push(key, buildProps(subject[key]));
} else {
result.push(key);
}
}
}
return result;
}
function stringify(input) {
var result = [];
for (var i = 0; i < input.length; i++) {
if (typeof input[i] == "string") {
result.push(input[i]);
} else {
result = result.concat(stringify(input[i]));
}
}
return result.join('.');
}
console.log(buildProps(scope));
console.log(stringify(buildProps(scope)));
Parse out the strings in the resulting array/sub-arrays, format it any way you like.
In my simple example I just list them in order:
query.filter.column.productName.price.table.productType.categoryId

Categories