Determine if array element represents an object or a value - javascript

From a JSON object (containing stock data), I want to add certain elements to an array (in Google Sheets script editor):
var quote = JSON.parse(response.getContentText());
// Example of what quote object looks like:
{
"quoteSummary": {
"result": [
{
"Profile": {
"sector": "Technology",
"website": "www.test.com"
},
"Owners": [
{
"name": "Eric",
"age": "28"
},
{
"name": "Susan",
"age": "44"
}
],
"Profit": 100,
"Assets": 7000
}
]
}
}
Here is my current approach to read only some specific values:
var arr = [];
arr.push(quote.quoteSummary.result[0].Profile.sector); // Technology
arr.push(quote.quoteSummary.result[0].Owners[1].name); // Susan
arr.push(quote.quoteSummary.result[0].Profit); // 100
But since there are many specific properties to read, I'd like to use a loop:
var quote = JSON.parse(response.getContentText());
var arr = [];
var el = [
['Profile', 'sector'],
['Owners[1]', 'name'],
['Profit']
];
for (i = 0; i < el.length; i++)
{
if (quote.quoteSummary.result[0][el[i][0]][el[i][1]] !== undefined)
{
arr.push(quote.quoteSummary.result[0][el[i][0]][el[i][1]].value);
}
}
/*
Expected output (if I would loop through arr):
Technology
Susan
100
*/
The point is that different stocks, will have different properties. So el might define some non-existing elements or properties. Assume (in a bit of an other way of defining el -- as I wrote, I'm plexible here.. perhaps the paths are the easiest):
var el = [
'Profile.website',
'Profile.name',
'Assets'
]
/*
Expected output:
www.test.com
<----- "name" doesn't exist!
7000
Notice that in this example, there is no property "name" in Profile,
so I'd like to add an empty element to arr
*/
But this does not work. What is a generic loop that accomplishes what I'm trying to do here? The array defining what I want can also be constructed differently if that helps. But the point is that I don't end up with a script like:
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);

I recommend you use variable-length chains of property names. Each name in a given chain represents a deeper property. You can "dive" into an object through an arbitrary number of property names with code like this:
let dive = (obj, propertyNames) => {
for (let pn of propertyNames) obj = obj[pn];
return obj;
};
Now you can say:
let dive = (obj, propertyNames) => {
for (let pn of propertyNames) obj = obj[pn];
return obj;
};
let quote = {
quoteSummary: {
result: [
{
Profile: {
sector: 'Technology',
website: 'www.test.com'
},
Owners: [
{
name: 'Eric',
age: '28'
},
{
name: 'Susan',
age: '44'
}
],
Profit: 100,
Assets: 7000
}
]
}
};
// Here are the "variable-length property chains":
let el = [
[ 'Profile', 'sector' ],
[ 'Owners', 1, 'name' ],
[ 'Profit' ]
];
// Here's how to combine `el`, `dive`, and your `quote` data to get a result:
let arr = el.map(propertyNames => dive(quote.quoteSummary.result[0], propertyNames));
console.log(arr);
You could even replace dive with Array.prototype.reduce, if you'd like to stay functional but avoid the function definition:
dive(someObj, propertyNames);
is equivalent to
propertyNames.reduce((obj, propName) => obj[propName], someObj);
Note the above code all assumes that a property exists for each term in the property chain (except the final property name, which may resolve to undefined without causing any errors). If some cases may have the, e.g., Profile key undefined or null you'll need to write some kind of if (propertyDoesntExist) / else statement which describes how to deal with missing properties.
For example you could modify dive to handle non-existent properties:
let dive = (obj, propertyNames, valueIfMissing=null) => {
for (let pn of propertyNames) {
// Intentional use of loose `==` operator!
if (obj == null) return valueIfMissing;
obj = obj[pn];
}
return obj;
};
This means that:
dive({ a: 1, b: 2, c: 3 }, [ 'd' ]) === null;
But we can substitute any default return value:
dive({ a: 1, b: 2, c: 3 }, [ 'd' ], 'ddd') === 'ddd';
Note this works at any depth:
dive({ a: { a: 1 }, b: { b: 2 }, c: { c: 3 }, d: null }, [ 'd', 'd' ]) === null;
dive({ a: { a: 1 }, b: { b: 2 }, c: { c: 3 }, d: null }, [ 'd', 'd' ], 'ddd') === 'ddd';

You should be able to do this:
if(y.value){
arr.push(y.value);
} else {
arr.push(y);
}
However, this will break if y.value happens to be something like 0, false, etc.
If this is the case you can do this:
if(y.hasOwnProperty("value")){
arr.push(y.value);
} else {
arr.push(y);
}
Based on the comment:
if (quote.quoteSummary.result[0][el[i][0]].hasOwnProperty("value")) {
arr.push(quote.quoteSummary.result[0][el[i][0]].value);
} else {
arr.push(quote.quoteSummary.result[0][el[i][0]]);
}

Related

Data from the object doesn't convert when using JSON.stringify

I'm stuck in this weird issue that I'm having hard time in understanding what's doing on. When a button is clicked it calls the function onSubmit. What onSubmit function should do is stringify is the object to JSON however for me this doesn't happen. The result I get when I console.log(JSON.stringify(obj)); is [[]]. When I console.log(obj); I can see the object.
I was not able to replicate the same issue in playcode.io and codesandbox.io
async function onSubmit() {
let l = [];
l["Channel"] = undefined;
l["MetricsData"] = [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
];
let obj = [];
l.Channel = 1;
obj.push(l);
console.log(obj);
console.log(JSON.stringify(obj)); //[[]]
}
As others have pointed in comments, you're instantiating l as an array and then attempt populating named keys (Channel, Metricsdata).
Note: Technically, arrays are objects, but they're special objects (their keys are intended to be numeric and they also have a few extra props and methods, specific to arrays - e.g: length, pop, push, slice, etc...). Use the link above to read more about arrays.
What you need to do is use l as an object (e.g: {}):
const l = {
Channel: 1,
MetricsData: [
{ id: 1, name: "CPA", year: "" },
{ id: 2, name: "Average Gift", year: "" },
{ id: 3, name: "Average Gift Upgrade %", year: "" }
]
};
// Uncomment any of the following:
// console.log('object:', l);
// console.log('stringified object:', JSON.stringify(l));
const items = [];
items.push(l);
// console.log('items:', items);
// console.log('first item:', items[0]);
console.log(JSON.stringify(items));

Inside an Object.value loop how can I get the key

I have to create match condition based on an array my array will look like below
var groupData={
A:[
{rollnum: 1, name:'Arya', age:15},
{rollnum: 2, name:'Aryan', age:15}
],
B:[
{rollnum:11, name:'Biba', age:15},
{rollnum:12, name:'Bimisha', age:15}
]
}
I am looping using for loop. How can reduce the loops. Can any one suggest me a proper way for this
Object.values(groupData).flat().forEach((rowitem)=>{
query={};
Object.keys(rowitem).forEach(eachField=>{
query[eachField]["$in"].push(rowitem[eachField])
});
fullarray[Object.keys(groupData)]=matchQuery;
})
I need an output (fullarray) like below
{
'A':{
rollnum:{'$in':[1,2]},
name: {'$in':['Arya', 'Aryan']},
age: {'$in':[15]}
},
'B':{
rollnum:{'$in':[11,12]},
name: {'$in':['Biba', 'Bimisha']},
age: {'$in':[15]}
}
}
Here 'A' 'B' is not coming correctly
Don't use Object.values() since that discards the A and B keys.
Use nested loops, one loop for the properties in the object, and a nested loop for the arrays.
You need to create the nested objects and arrays before you can add to them.
var groupData = { A:
[ { rollnum: 1,
name: 'Arya',
age:15},
{ rollnum: 2,
name: 'Aryan',
age:15}, ],
B:
[ { rollnum: 11,
name: 'Biba',
age:15},
{ rollnum: 12,
name: 'Bimisha',
age:15} ] }
result = {};
Object.entries(groupData).forEach(([key, arr]) => {
if (!result[key]) {
result[key] = {};
}
cur = result[key];
arr.forEach(obj => {
Object.entries(obj).forEach(([key2, val]) => {
if (!cur[key2]) {
cur[key2] = {
"$in": []
};
}
cur[key2]["$in"].push(val);
});
});
});
console.log(result);

JS Howto define function on subobjects

I have an object containing nested objects, lets say:
var root = {
'areas' : [
{ 'areaName' : 'A1',
'modules' : [ { 'moduleName' : 'M1', moduleType : 'F' },
{ 'moduleName' : 'M2', moduleType : 'F' }] },
{ 'areaName' : A2,
'modules' : [ { 'moduleName' : 'M1', moduelType : 'B' },
{ 'moduleName' : 'M2', moduleType : 'F' }] }
];
}
This is a Json returned from a WS. I want to define a functions that can be called upon modules. Lets say:
root.A1.M2.Foo();
1) Can this be done in a fashionably way? I could iterate over all the objects in a for loop and add the function for each object. But I was looking more in the direction of using a prototype or something alike.
2) Can it be done to define this function so it has knowledge of its position in the hierarchy? So that you can do:
function Foo(){
var module = ... ('this', I assume)
var area = ...
var root = ...
}
3) Bonus-question: Can it be archived to add the function only if a certain condition is met? So that Foo() is only added to modules with moduleType == 'F' and Bar() to modules with moduleType == 'B'
root.A1.M1.Foo(); // good
root.A2.M1.Bar(); // good
root.A2.M2.Bar(); // bad
I do have control about the WS, so returning the object in another format is possible, although not preferable.
Looking forward to your creative solutions!
A good result should be reached using ECMASCRIPT-6 Proxies as described here: https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Proxy
unfortunately, the current support of es6 proxies isn't good but almost all vendors are working to make it available as soon as possible. https://kangax.github.io/compat-table/es6/
this is a google polyfill: https://github.com/GoogleChrome/proxy-polyfill
An attempt to get what you want. This goes straigth to point 3, returning a function with dependent name. the reurn value is the path, it could be all other reference to this object or another.
Your point 2 is a bit vague, where should
var module = ... ('this', I assume)
var area = ...
var root = ...
point to?
And another question arised, is the structure fixed? are the properties fixed? ans where should the result point to? To the original object or like this proposal to a different one?
function iter(o, r, key, a) {
key = key || 'root';
a = a || [key];
Object.keys(o).forEach(function (k) {
if (~k.indexOf('Name')) {
a = a.slice();
r[key] = r[key] || {};
r = r[key];
key = o[k];
a.push(o[k]);
return;
}
if (~k.indexOf('Type')) {
r[key] = {};
r[key][{ F: 'Foo', B: 'Bar' }[o[k]]] = function () {
return a;
};
return;
}
if (Array.isArray(o[k])) {
o[k].forEach(function (b) {
iter(b, r, key, a);
});
return;
}
if (typeof o[k] === 'object') {
iter(o[k], r, key, a);
}
});
}
var object = { root: { areas: [{ areaName: 'A1', modules: [{ moduleName: 'M1', moduleType: 'F' }, { moduleName: 'M2', moduleType: 'F' }] }, { areaName: 'A2', modules: [{ moduleName: 'M1', moduleType: 'B' }, { moduleName: 'M2', moduleType: 'F' }] }] } },
result = {};
iter(object, result);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
document.write(result.root.A1.M1.Foo() + '<br>');
document.write(result.root.A1.M2.Foo() + '<br>');
document.write(result.root.A2.M1.Bar() + '<br>');
document.write(result.root.A2.M2.Foo() + '<br>');
1) array.prototype.map() can help you. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map
var test = [{id : 1}, {id : 2}];
var newTest = test.map(function (i) {i.value = 'a'});
console.log(newTest);
// [{id : 1, value : 'a'}, {id : 2, value : 'a'}]
2 & 3) I don't understand, sorry, language limits
You can think about the object you receive from sockets, like about raw data that must be deserialized [1]. So you can define a factory [2] that accepts this raw data and constructs objects with format and methods that you need. As a bonus this will help you decouple data representation from logic, so in future you can change format of data you sending via network with less effort.
https://en.wikipedia.org/wiki/Serialization
https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)

node.js + Object Array

In my node app i have to construct an object array from another object array.
Consider my object array as..
[ { id_0: 356, id_1: 33, name_1: 'aaaa' },
{ id_0: 756, id_1: 89, name_1: 'bbbbb' },
{ id_0: 456, id_1: 89, name_1: 'ccccc' },
{ id_0: 356, id_1: 27, name_1: 'dddd' } ]
I have to construct an object array as like below:
[{
"356":["33":"aaaa","27":"ddddd"],------------->Changes made
"456":[{"89":"cccc"}],
"756":[{"89":"bbbbbbbb"}]
}]
I tried using async.map.But i cant get the right way to do it.Please help me to solve this.Thanks in advance...
You can use Array.prototype.reduce function, like this
console.log(data.reduce(function(result, current) {
var obj = {};
result[current.id_0] = result[current.id_0] || [];
obj[current.id_1] = current.name_1;
result[current.id_0].push(obj);
return result
}, {}));
Output
{ '356': [ { '33': 'aaaa' }, { '27': 'dddd' } ],
'456': [ { '89': 'ccccc' } ],
'756': [ { '89': 'bbbbb' } ] }
If you want to convert this to an array of object, just wrap the result of data.reduce with [] like this
console.log([data.reduce(function(result, current) {
...
...
}, {})]);
Edit:
result[current.id_0] = result[current.id_0] || [];
this line makes sure that result[current.id_0] is an array. If the value of result[current.id_0] is truthy, then that value is rturned but if it is not, then [] will be returned. So, a new array will be created and assigned to result[current.id_0]. It is actually a shorthand for
if (result.hasOwnProperty(current.id_0) === false) {
result[current.id_0] = [];
}
Edit 2: If you like to keep the grouped elements as an object, you could do like this
console.log(data.reduce(function(result, current) {
result[current.id_0] = result[current.id_0] || {};
result[current.id_0][current.id_1] = current.name_1;
return result
}, {}));
Output
{ '356': { '27': 'dddd', '33': 'aaaa' },
'456': { '89': 'ccccc' },
'756': { '89': 'bbbbb' } }

Sort array of objects by arbitrary list in JavaScript

Given an array of objects like this:
objects = [
{ id: 'aaaa', description: 'foo' },
{ id: 'bbbb', description: 'bar' },
{ id: 'cccc', description: 'baz' }
];
And an array of strings like this:
order = [ 'bbbb', 'aaaa', 'cccc' ];
How would I sort the first array so that the id attribute matches the order of the second array?
Try this:
objects.sort(function(a, b){
return order.indexOf(a.id) - order.indexOf(b.id)
});
Assuming the variables are like you declared them in the question, this should return:
[
{ id: 'bbbb', description: 'bar' },
{ id: 'aaaa', description: 'foo' },
{ id: 'cccc', description: 'baz' }
];
(It actually modifies the objects variable)
You need a way to translate the string into the position in the array, i.e. an index-of function for an array.
There is one in newer browsers, but to be backwards compatible you need to add it if it's not there:
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(str) {
var i;
for (i = 0; i < this.length; i++) if (this[i] == str) return i;
return -1;
}
}
Now you can sort the array by turning the string into an index:
objects.sort(function(x,y){ return order.indexOf(x.id) - order.indexOf(y.id); });
Demo: http://jsfiddle.net/Guffa/u3CQW/
Use a mapping object for (almost) constant access time:
/* Create a mapping object `orderIndex`:
{
"bbbb": 0,
"aaaa": 1,
"cccc": 2
}
*/
const orderIndex = {}
order.forEach((value, index) => orderIndex[value] = index);
// Sort
objects.sort((a, b) => orderIndex[a.id] - orderIndex[b.id]);
// data
const objects = [
{ id: 'aaaa', description: 'foo' },
{ id: 'bbbb', description: 'bar' },
{ id: 'cccc', description: 'baz' }
];
const order = [ 'bbbb', 'aaaa', 'cccc' ];
/* Create a mapping object `orderIndex`:
{
"bbbb": 0,
"aaaa": 1,
"cccc": 2
}
*/
const orderIndex = {}
order.forEach((value, index) => orderIndex[value] = index);
// Sort
objects.sort((a, b) => orderIndex[a.id] - orderIndex[b.id]);
// Log
console.log('orderIndex:', orderIndex);
console.log('objects:', objects);

Categories

Resources