Flatten array into single dynamic object using Javascript - javascript

I want to extract object keys into a single array and values in a different array so that I can paste the headers and values into a google sheet.
I want to achieve a dynamic code so that if more fields are pulled in from the API, it can map headers with values.
//API Response Sample.
var data = [
{
"actions": [
{
"action_type": "comment",
"value": "3"
},
{
"action_type": "like",
"value": "33"
},
{
"action_type": "link_click",
"value": "1531"
},
{
"action_type": "mobile_app_install",
"value": "1049"
}
],
"spend": "8621.03",
"date_start": "2017-10-28",
"date_stop": "2017-11-26"
}
]
So far the below code is fixed not dynamic.
const sheet = SpreadsheetApp.getActiveSheet();
//flatten the objects
var actionObjects = data.map(returnAction)
//get the headers
var headers = Object.keys(actionObjects[0])
//create a 2D array for rows
var actionRows = actionObjects.map(a => headers.map(h => a[h]))
//write the headers
sheet.getRange(sheet.getLastRow() + 1, 1, 1, headers[0].length).setValues([headers]);
//write the rows
sheet.getRange(sheet.getLastRow() + 1, 1, actionRows.length, actionRows[0].length).setValues(actionRows);
}
function returnAction(data){
let action = {}
data.actions.forEach(a => action[a.action_type] = a.value)
action ['spend'] = data.spend
action ['date_start'] = data.date_start
action ['date_stop'] = data.date_stop
return action
}

Object keys into array:
const keys = Object.keys(obj);
Object values into array:
const values = Object.values(obj);
Or both in one go ...
const keys = [];
const values = [];
for (const [key,value] of Object.entries(obj)) {
keys.push(key);
values.push(value);
}
If the structure of your object does not change... maybe something like this?
const action = {};
data.forEach(obj => {
for (const [key,value] of Object.entries(obj)) {
if (Array.isArray(value)) {
for (const o of value) {
const a = Object.values(o);
action[a[0]] = a[1];
}
} else action[key] = value;
}
})
Try this:
function setResult() {
const sheet = SpreadsheetApp.getActiveSheet();
class getResults {
constructor(arr) {
this.headers = {};
this.results = [];
for (const obj of arr) {
const actions = {};
for (const [header,value] of Object.entries(obj)) {
if (Array.isArray(value)) {
for (const action of value) {
const values = Object.values(action);
actions[values[0]] = values[1];
this.headers[values[0]] = values[0]
}
} else {
actions[header] = value;
this.headers[header] = header;
}
}
this.results.push(actions);
}
}
get array() {
const headers = Object.keys(this.headers);
const results = [headers];
for (const action of this.results) {
results.push(headers.map(header => !!action[header] ? action[header] : ''));
}
return results;
}
}
const values = new getResults(data).array;
sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
}
This is a whole function which takes in the 'data' array-object and split it out onto your spreadesheet.
Explanation:
This function is mainly written in Object constructor and Classes.
more about object constructor in JavaScript
more about classes in JavaScript
According to your sample data, there are Objects inside an Array which I believe that each of those Objects are one set of data.
So, the 1st step is using a for ... of loop to work with each data set separately with this line of code for (const obj of arr) {}, this is very much the samething as the var actionObjects = data.map(returnAction) line in your original code. more about for ... of in JavaScript
With each of your data object, it has 2 main structure,
ONE is Array: Object: {Key1: Value1, Key2: Value2},
which you want Value1 as header and Value2 as the actual value in the output.
TWO is simply Key: Value pairs where you need key as the header and value as the value as output.
To work with the given slice of data set, this line for (const [header,value] of Object.entries(obj)) {} uses another for...of loop together with Object.entries() function to deconstruct the given Object into an 2D Array where each of the array value is one Array containing a pair of [key,value] form the given object. more about Object.entries()
Afterwards, if (Array.isArray(value)) {} will check each value given by the for...of Object.entries() function, if it is an Array, it met the condition ONE, else it is condition TWO.
For condition ONE, you want to use the 1st value of the object as header, and the 2nd value as actual value.
for (const action of value) {} iterate the 'values' of the object as an array, and store the values as {value[0]: value[1]} in the object declared before entering the loop fucntion for later use.
For condition TWO, just store it into the same object as condition ONE uses in the format of {key: value}.
At the end of each loop, before going onto the next data object, push the key: value pairs stored in this loop (named as actions) into result array.
Till this step, you alread have an array which looks like this:
Array: [
Object1: {
header1: value1,
header2: value2,
header3: value3,
header4: value4,
...
},
...
]
The Object this.header {} is declarated to keep track of the length of max header column counts, and get rip of any duplicates (since Object keys cannot be duplicated). This help keep the function working even if some of your data objects may has different headers from the others.
After these loops iterate every object inside your data Array,
custom method created with getter function get array() form the final result of all data into a 2D array for the apps script .setValues() function to print it onto your spreadsheet. more about getter
If your main concern is the class and object constructor, here is another version of code without using any of them:
function setResult2() {
const sheet = SpreadsheetApp.getActiveSheet();
const headers = {};
const results = [];
for (const obj of data) {
const actions = {};
for (const [header,value] of Object.entries(obj)) {
if (Array.isArray(value)) {
for (const action of value) {
const values = Object.values(action);
actions[values[0]] = values[1];
headers[values[0]] = values[0]
}
} else {
actions[header] = value;
headers[header] = header;
}
}
results.push(actions);
}
const getArray = (results,headers) => {
const headers_final = Object.keys(headers);
const results_final = [headers_final];
for (const action of results) {
results_final.push(headers_final.map(header => !!action[header] ? action[header] : ''));
}
return results_final;
}
const values = getArray(results,headers);
console.log(values)
sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
}

Related

How to convert json object keys into different arrays removing the duplicate

I'm having the JSON like this i need to group this JSON with all the keys in JSON object and value should in array (excluding duplicates).
var people = [
{sex:"Male", name:"Jeff"},
{sex:"Female", name:"Megan"},
{sex:"Male", name:"Taylor"},
{sex:"Female", name:"Madison"}
];
My output should be like
{"sex":["Male","Female"],"name":["Jeff","Megan","Taylor","Madison"]}
how we can able to achieve this
function getValues(array) {
var result = {};
array.forEach(obj => {
Object.keys(obj).forEach(key => {
if(!Array.isArray(result[key]))
result[key] = [];
result[key].push(obj[key]);
})
})
return result;
}
You could use the Array.reduce() method to transform your array into a single object:
var people = [
{sex:"Male", name:"Jeff"},
{sex:"Female", name:"Megan"},
{sex:"Male", name:"Taylor"},
{sex:"Female", name:"Madison"}
];
const transformed = people.reduce((acc, e) => {
Object.keys(e).forEach((k) => {
if (!acc[k]) acc[k] = [];
if (!acc[k].includes(e[k])) acc[k].push(e[k]);
});
return acc;
}, {});
console.log(transformed);
If for one of the object keys (sex or name in this case) a value array does not exist, it is created. Before a value is pushed into any of the value arrays, it is verified that it is not already present in that array.

How to restart the numeration of the keys in a object?

What is the problem :
this are my data stored in this.state.jsondata :
{
label{0:1,1:1}
prediction{0:0,1:1}
text{0:"abc",1:"def"}
}
This comes front JSON.stringify(this.state.jsondata) :
{"label":{"1":0,"2":0,"3":0,"4":0},"text":{"1":"aa","2":"bb,"3":"cc","4":"dd"},"prediction":{"1":2,"2":2,"3":2,"4":2}}
when I delete one element using this code :
removeData = (keyToRemove) => {
// create a deep copy of the object
const clonedJsonData = JSON.parse(JSON.stringify(this.state.jsondata))
// now mutate this cloned object directly
Object.keys(clonedJsonData).forEach(key => delete clonedJsonData[key][keyToRemove])
// set the state
this.state.jsondata = clonedJsonData
this.state.jsondata becomes :
{
label{1:1}
prediction{1:1}
text{1:"def"}
}
instead of starting back to 0 like :
{
label{0:1}
prediction{0:1}
text{0:"def"}
}
So basically I want to set the key back to "0, 1, 2, 3.."
What have I tried :
I tried to iterate through my data but there is something wrong in it with the way I loop through it :
removeData = (keyToRemove) => {
// create a deep copy of the object
const clonedJsonData = JSON.parse(JSON.stringify(this.state.jsondata))
// now mutate this cloned object directly
Object.keys(clonedJsonData).forEach(key => delete clonedJsonData[key][keyToRemove])
// reset keys value
let i = 0
Object.keys(clonedJsonData).forEach(function(key) {
Object.keys(clonedJsonData[key]).forEach(function(k) {
var newkey = i++;
clonedJsonData[key][k] = clonedJsonData[key][k];
delete clonedJsonData[key][k];
})
})
// set the state
this.state.jsondata = clonedJsonData
}
What is the expected output ?
The expected output is :
{
label{0:1}
prediction{0:1}
text{0:"def"}
}
If your key basically represents the index of the value, why not using an array?
{ label: [1,1], prediction: [0,1], text: ["abc","def"] }
If you now delete the first entry in each array, data.text[0] will be "def" and so on...
Edit:
If you don't want to convert the data, you gotta be carefull, especially if you parse and stringify the whole thing around and then loop over the keys. They don't have to be in the same order afterwards.
Instead of cloning the object, deleting the key you want to remove and then updating all keys, you could also copy the object manually and leaving the key to remove out
var data = {label: {0:0, 1:0}, prediction: {0:0, 1:1}, text: {0:'abc',1:'def'}};
console.log("Original Data");
console.log(data);
removeData = (data, keyToRemove) => {
var ret = {};
Object.keys(data).forEach(key=>{
ret[key] = {};
let idx = 0;
for (let dataIdx = 0; dataIdx < Object.keys(data[key]).length; dataIdx++) {
if (dataIdx.toString() !== keyToRemove) {
ret[key][idx.toString()] = data[key][dataIdx.toString()];
idx++;
}
}
});
return ret;
}
var reducedData = removeData(data,"0");
console.log("Reduced Data");
console.log(reducedData);
Note that I don't loop over the keys, so I don't get the order mixed up.
Note that I also removed the side effects from your original function by handing over the data object as parameter.
So that's how I made it
removeData = (keyToRemove) => {
// create a deep copy of the object
const clonedJsonData = JSON.parse(JSON.stringify(this.state.jsondata))
// now mutate this cloned object directly
Object.keys(clonedJsonData).forEach(key => delete clonedJsonData[key][keyToRemove])
// reset keys value
let i = 0
Object.keys(clonedJsonData).forEach(function eachKey(key) {
i = 0
Object.keys(clonedJsonData[key]).forEach(function eachKey(kkey) {
var newkey = i++;
clonedJsonData[key][newkey] = clonedJsonData[key][kkey];
});
delete clonedJsonData[key][i];
});
// set the state
this.state.jsondata = clonedJsonData
}

Converting an array of objects into readable strings?

I'm trying to convert an array of objects into a regular array that I can compare values against.
$scope.refRFRInfo
Array(6) [Object, Object, Object, …]
{Object {rfrInformationID: 3000, fiscalYear: 2020, rfrNumber: NaN, …}
So I have this:
let newArray = $scope.Info.map(a => Object.keys(a).map(k => a[k]))
And the results come out as:
Array(6) [Array(18), Array(18), Array(18), …]
Array(18) [3000, 2020, NaN, …]
I want the values to be labeled as their type, so "rfrInformationID" "fiscalYear" and "rfrNumber." But I don't know how to do this.
Essentially, I want to compare a single "fiscalYear" input to a list of all the $scope.Info.fiscalYear values. But it won't let me select the values for comparison when they are an object. I also need the object to remain an object so I can sort on table headers with the object intact. If there is an easier way to achieve the end result, please let me know.
EDIT:
Based on Object.entries, I came up with:
var map = new Map(Object.entries(results.data));
map.forEach(([key, value]) => $scope.rfrInfo.push(`${key}: ${value}`));
But the second line breaks. Looks to be nesting my object into another object, too.
EDIT: Here is the full function I'm working with
function init() {
ContractsService.getRFRInformation()
.then(function (results) {
$scope.Info = results.data;
angular.forEach($scope.Info, function (value) {
$scope.number.push(value.rfrNumber);
}
});
$scope.loading = false;
});
}
In the "forEach" above I passed the value of the rfrNumber only. There are 14 fields in my $scope.Info object.
Here's what I've tried:
$scope.newInfo = [];
//Closest to intended result, but doesn't set names like "fiscalYear" or "rfrNumber" so
//I can't use it for validation
$scope.Info.forEach(_ => _.rfrNumber = (+_.Number));
let newArray = $scope.Info.map(a => Object.keys(a).map(k => a[k]));
newArray;
//Produces result where everything is further nested
var map = new Map(Object.entries(results.data));
map;
//None of these are working
map.forEach(([key, value]) => $scope.newInfo.push('${key}: ${value}'));
for (var { Object: { rfrInformationID: ID, fiscalYear: FY, rfrNumber: number } } of results.data) {
$scope.newInfo.push(Object);
}
JSON.stringify(map);
map;
JSON.stringify(results.data);
results.data;
let {
results: [
{
rfrInformationID: ID,
fiscalYear: FY,
rfrNumber: number
},
],
} = metadata;
}
var arr = [];
for (var i in results.data) {
if (obj.hasOwnProperty(i)) {
arr.push([i, results.data[i]]);
}

Compare key values within object for duplicate updated

This is a follow up post from Compare key values within object for duplicate for a follow up answer.
I have an object:
myObj = {
attendent-0-id:"123",
attendent-0-name:"Bob Smith",
attendent-1-id:"1234",
attendent-1-name:"Alex Smith",
attendent-2-id:"123",
attendent-2-name:"Charlie Smith",
attendent-0-id:"123",
attendent-0-name:"John Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
Thanks to help on here (Rick) I was able to get 90% of the way there.
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
map[value] = [map[value], key];
} else {
map[value] = key
}
}
}
return map;
}
I am getting a return of:
array:[
0:(2) ["attendent-0-name", "attendent-1-name"]
1:"attendent-2-name"
]
but I am looking for:
array:[
0:(2) ["attendent-0-name", "attendent-1-name", "attendent-2-name"]
]
The issue I am having is that while this works if there are two matching keys it will not work (Correctly) if there are three or more.
If you want to have an array of all matches for each key in you map, you need to start by setting an array when you find a key the first time. On subsequent matches, just push into that array:
const myObj = {'attendent-0-id': "1234",'attendent-0-name': "Bob Smith",'attendent-1-id': "123",'attendent-1-name': "Alex Smith",'attendent-2-id': "123",'attendent-2-name': "Charlie Smith",'attendent-maxGuest': 1,'attendent-party-name': "",};
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
map[value].push(key); // push a new value
} else {
map[value] = [key] // set it to an array
}
}
}
/* if you only want lengths > 1
otherwise just return map */
let filtered = Object.entries(map)
.reduce((a, [key, value]) => value.length > 1 ? Object.assign(a, {[key]: value}) : a, {})
return filtered;
}
console.log(errorOnDuplicateIds(myObj));
If you are only interested in values with more than one hit, you can reduce() down to a map with only values of length greater than one, which is what the last bit in the snippet does.

Transformation array to nested object

I try to convert some data into a javascript object. The data looks like this:
data = [["a","a","a","value1"],
["a","a","b","value2"],
["a","b","value3"],
["a","c","a","value4"]]
What I want to get is:
a = {
"a":{
"a":"value1",
"b":"value2"
},
"b":"value3",
"c":{
"a":"value4"
}
}
Since the amount of nested attributes varies I do not know how to do this transformation.
This should be the function you're looking for:
function addItemToObject(dict, path){
if (path.length == 2){
dict[path[0]] = path[1];
} else {
key = path.shift()
if (! dict[key]) {
dict[key] = {};
}
addItemToObject(dict[key], path);
}
return dict;
}
var result = data.reduce(addItemToObject,{});
The function addItemToObject is a recursive function which creates the depth and inserts the value.
This is applied to everything in data using reduce;
Here's a solution using Ramda.js
const data = [
["a","a","a","value1"],
["a","a","b","value2"],
["a","b","value3"],
["a","c","a","value4"]
]
const transformData =
R.pipe(
R.map(R.juxt([R.init, R.last])),
R.reduce(
(obj, [path, value]) =>
R.assocPath(path, value, obj),
{},
),
)
console.log(transformData(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
I don't get your desired solution as there are 4 blocks of data but only 3 properties in the final object.
However, this is how you can iterate through an array and its child arrays:
var data = [["a","a","a","value1"],
["a","a","b","value2"],
["a","b","value3"],
["a","c","a","value4"]];
//Store your results in here
var result = {};
//Iterate each block of data in the initial array
data.forEach(function(block){
//block will refer to an array
//repeat with the child array
block.forEach(function(item){
//item will point to an actual item in the child array
});
});
forEach() will call a provided function on each item within an array.

Categories

Resources