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
}
Related
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);
}
I have a data response of form:
claim_amount_arr: [218691.44]
claim_approval_status: ["In Process"]
percentages_claim: [1]
percentages_claim_amount: [1]
total_claim_arr: [2]
_id: 0
__proto__: Object
I want to convert it to array so as to map it into table further in a component. Since it does not have a key, I am not able to access it's key value pair for mapping.
I tried the following approach but then it eliminates all the key from the array:
const summary_props = this.props.summary
//console.log(summary_props); //this console gives me data as shown in image above
const sortedvalue = Object.keys(summary_props).map(key => {
return summary_props[key];
});
console.log(sortedvalue);
output of this console:
Please help.
class ClaimInformation()
{
constructor(data,index)
{
this.claim_amount = data.claim_amount_arr[index];
this.claim_approval_status = data.claim_approval_status[index];
this.percentage_claim = data.percentage_claim[index];
this.percentages_claim_amount = data.percentages_claim_amount[index];
this.total_claim = data.total_claim_arr[index];
}
}
var claims = [];
for(let i = 0; i < response.claim_amount_arr.length; i++){
claims.push(new ClaimInformation(response,i));
}
Try Object.entries().
In short, it can transform an object into an array.
Edit: More specific here
Object.entries(formData).map(([key, value]) => {
//Now you can access both the key and their value
})
I have a list of keys and a value. For example:
keys = ["keyA", "keyB", "keyC"];
value = 100;
I'm trying to create a function to create a map so that:
map["keyA"]["keyB"]["keyC"] = 100;
I assumed this was the best data structure based on the answer given here:
Anyway, the part that challenges me is that I need a function that will create a map for any number of keys. I've tried doing this in a loop but can't get it to work because I don't know how to access different levels of my map, but it also feels sloppy:
for(var i=0; i<keys.length; i++){
for(var j=0; j<i; j++){
maps[keys[0]]...[keys[j]] = {};
if(j+1 === i){
maps[keys[0]]...[keys[j]][keys[i]] = value;
}
}
}
How can I create my map?
You can try to store a reference to the last created inner object, and go deeper in a loop, in order to make it in a linear time:
// Input data:
var keys = ["keyA", "keyB", "keyC", "keyD", "keyE"];
var value = 100;
// Algorithm:
var result = {};
var last = result;
for (var i = 0; i < keys.length - 1; i++)
{
last = (last[keys[i]] = {});
// can be change to a two-liner:
// last[keys[i]] = {};
// last = last[keys[i]];
}
last[keys[keys.length - 1]] = value;
// Output:
document.body.innerHTML = JSON.stringify(result);
document.body.innerHTML += "<br/><br/>" + result["keyA"]["keyB"]["keyC"]["keyD"]["keyE"];
If you do not want to maintain a hierarchy of objects, I would suggest you concatenate the keys and store the value with the concatenated string as key.
This assumes you always have the same keys array. If your keys array is supplied externally, you can sort before joining.
See the snippet.
var keys = ["keyA", "keyB", "keyC", "keyD", "keyE"];
var value = 568;
var datastructure = {};
datastructure[keys.join("-")] = value;
document.getElementById("output").innerHTML = datastructure[keys.join("-")];
<span id="output"></span>
Assuming this structure is to be a tree where the nesting has arbitrary depth, first you might benefit from a helper function that lets you access possible non-existent paths safely:
function path(obj, str) {
return str.split('.').reduce(function (acc, key) {
return acc instanceof Object ? acc[key] : undefined;
}, obj);
}
And you also want a way to set such paths neatly:
function setPath(obj, str, val) {
var path = str.split('.');
var key = path.pop();
var target = path.reduce(function(acc, key) {
return acc[key] = acc[key] instanceof Object ? acc[key] : {};
}, obj);
target[key] = val;
}
Then you have a clean interface for storing and retrieving this data.
map = {};
setPath(map, 'keyA.keyB.keyC', 100);
path(map, 'keyA.keyB.keyC') // 100;
path(map, 'keyA.keyX.keyY') // undefined;
If you prefer, you could have it take arrays of keys instead of dot-notation paths as shown here (just omit the split steps).
Note that if you are never interested in accessing nodes in the tree other than the leaves, or wish to be able to have values for both map.a.b and map.a, you can do this much more simply by having a single depth:
map[keys.join('.')] = 100;
And since you've added in a comment that the objective here is actually just to associate a value with a set of keys, and that there is no actual tree structure at all:
function get(map, keys) {
var key = keys.sort().join('.');
return map[key];
}
function set(map, keys, val) {
var key = keys.sort().join('.');
map[key] = val;
}
If periods are plausible characters in your keys, substitute a different character that you can safely reserve.
Edit 24/12/2022
I have created an ES module for order agnostic multi map. I will explain here how you can set it up for the OP's use case.
https://github.com/martian17/ds-js
First you will want to clone the repository to your project, or copy the code.
$ git clone https://github.com/martian17/ds-js.git
Here is an example use case
// import it to your project
import {OrderAgnosticMultiMap} from "path_to_ds-js/multimap.mjs";
// Instantiate
const map = new OrderAgnosticMultiMap();
// Register values
map.set("keyA", "keyB", "keyC", "content 1");
map.set("keyA", "keyC", "keyC", "content 2");
map.set("keyA", "keyB", "keyB", "content 3");
// The keys can be any object
map.set(map, OrderAgnosticMultiMap, map, window, document, "content 4");
// Get values (keys can be in different orders)
console.log(map.get("keyB", "keyC", "keyA"));
// log: "content 1"
console.log(map.get("keyB", "keyB", "keyC"));
// log: undefined
map.set(document, map, window, OrderAgnosticMultiMap, map);
// log: "content 4"
// Check if a value exists for some keys
console.log(map.has("keyC", "keyC", "keyA"));
// log: true
console.log(map.has("keyA", "keyC", "keyA"));
// log: false
// Loop through values
for(let [tally,value] of map){
console.log(tally,value);
}
// log:
// Map(3) {"keyA" => 1, "keyB" => 1, "keyC" => 1} 'content 1'
// Map(3) {"keyA" => 1, "keyC" => 2} 'content 2'
// Map(3) {"keyA" => 1, "keyB" => 2} 'content 3'
// Map(3) {map => 2, OrderAgnosticMultiMap => 1, window => 1, document => 1} 'content 4'
// Delete keys
map.delete("keyC", "keyB", "keyA");
map.delete("keyB", "keyB", "keyA");
map.delete("keyC", "keyC", "keyA");
console.log(map.has("keyC", "keyC", "keyA"));
// log: false
Pre-edit
If there is anyone wondering if there is a solution for multi keyed ES6 map, here's my take.
The order does matter though, so map.get(a,b,c) and map.get(c,a,b) will fetch different values.
And you can of course use this as string to object map, so it satisfies the OP's use case as well.
class MultiMap{
map = new Map;
own = Symbol();// unique value that doesn't collide
set(){
let lst = [...arguments];
let val = lst.pop();
let map = this.map;
for(let k of lst){
if(!map.has(k))map.set(k,new Map);
map = map.get(k);
}
map.set(this.own,val);// to avoid collision between the same level
return val;
}
get(...lst){
let map = this.map;
for(let k of lst){
if(!map.has(k))return undefined;
map = map.get(k);
}
return map.get(this.own);
}
has(...lst){
let map = this.map;
for(let k of lst){
if(!map.has(k))return false;
map = map.get(k);
}
return map.has(this.own);
}
delete(...lst){
let map = this.map;
let maps = [[null,map]];
for(let k of lst){
if(!map.has(k))return false;
map = map.get(k);
maps.push([k,map]);
}
let ret = map.delete(this.own);
for(let i = maps.length-1; i > 0; i--){
if(maps[i][1].size === 0){
maps[i-1][1].delete(maps[i][0]);
}else{
break;
}
}
return ret;
}
}
Example use case
let a = {a:"a"};
let b = {b:"b"};
let c = {c:"c"};
let mm = new MultiMap;
//basic operations
console.log(mm.set(a,b,c,"abc"));// "abc"
console.log(mm.get(a,b,c));// "abc"
console.log(mm.has(a,b,c));// true
console.log(mm.delete(a,b,c));// true
// overlapping keys can be handled fine as well
mm.set(a,b,"ab");
mm.set(a,"a");
console.log(mm.get(a,b));// "ab"
console.log(mm.get(a));// "a"
For anyone curious about my use case: I was trying to make an event listener wrapper that maps to multiple events internally (mousedown => mousedown, touchstart etc). I needed to cache the arguments when .on() is called so .off() can find the right set of event listeners to remove.
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.
I have this object:
key = {
spawn:{type:1,img:app.assets.get('assets/spawn.svg')},
wall:{type:2,img:app.assets.get('assets/wall.svg')},
grass:{type:3,img:app.assets.get('assets/grass.svg')},
spike:{type:4,img:app.assets.get('assets/spike.svg')},
ground:{type:5,img:app.assets.get('assets/ground.svg')}
};
And I have an array with only types and I need to add the given image to it, the array looks something like this:
[{type:1,image:null},{type:3,image:null},{type:2,image:null},{type:2,image:null},{type:5,image:null}]
Basically I want to loop the array, find the type in the key object and get the given image and save it into the array.
Is there any simple way to do this?
One thing that stands out here for me is the line
...get the given image and save it into the array
I'm assuming this means the original array. I think a better approach would be to map the appropriate keys and values to a new array but I've assumed, for this example, that it's a requirement.
In an attempt to keep the solution as terse as possible and the request for a lodash solution:
_.each(key, function(prop){
_.each(_.filter(types, { type: prop.type }), function(type) { type.image = prop.img });
});
Given the object of keys and an array of objects like so:
var key = {
spawn:{type:1,img:app.assets.get('assets/spawn.svg')},
wall:{type:2,img:app.assets.get('assets/wall.svg')},
grass:{type:3,img:app.assets.get('assets/grass.svg')},
spike:{type:4,img:app.assets.get('assets/spike.svg')},
ground:{type:5,img:app.assets.get('assets/ground.svg')}
};
var arr = [{type:1,image:null},{type:3,image:null},{type:2,image:null},{type:2,image:null},{type:5,image:null}];
We can first create an array of the properties in the object key to make iterating it simpler.
Then loop over the array arr, and upon each member, check with a some loop which image belongs to the member by its type (some returning on the first true and ending the loop).
You can change the forEach to a map (and assign the returned new array to arr or a new variable) if you want the loop to be without side-effects, and not to mutate the original array.
var keyTypes = Object.keys(key);
arr.forEach(function (item) {
keyTypes.some(function (keyType) {
if (key[keyType].type === item.type) {
item.image = key[keyType].img;
return true;
}
return false;
});
});
The smarter thing would be to change the object of the imagetypes so that you could use the type as the accessing property, or create another object for that (as pointed out in another answer).
I'm not sure if this solution is modern, but it does not use any loops or recursion.
object = {
spawn: {type:1, img:app.assets.get('assets/spawn.svg')},
wall: {type:2, img:app.assets.get('assets/wall.svg')},
grass: {type:3, img:app.assets.get('assets/grass.svg')},
spike: {type:4, img:app.assets.get('assets/spike.svg')},
ground: {type:5, img:app.assets.get('assets/ground.svg')}
};
arr = [
{type:1, image:null},
{type:3, image:null},
{type:2, image:null},
{type:2, image:null},
{type:5, image:null}
];
var typeImages = {};
Object.getOwnPropertyNames(object).forEach(function(value){
typeImages[object[value].type] = object[value].img;
});
arr = arr.map(function(value){
return {
type: value.type,
image: typeImages[value.type]
};
});
var key = {
spawn:{type:1,img:app.assets.get('assets/spawn.svg')},
wall:{type:2,img:app.assets.get('assets/wall.svg')},
grass:{type:3,img:app.assets.get('assets/grass.svg')},
spike:{type:4,img:app.assets.get('assets/spike.svg')},
ground:{type:5,img:app.assets.get('assets/ground.svg')}
};
var typesArray = [{type:1,image:null},{type:3,image:null},{type:2,image:null},{type:2,image:null},{type:5,image:null}];
for(var i = 0, j = typesArray.length; i < j; i++)
{
typesArray[i].image = getKeyObjectFromType(typesArray[i].type).img;
}
function getKeyObjectFromType(type)
{
for(var k in key)
{
if(key[k].type == type)
{
return key[k];
}
}
return {};
}
for (var i = 0; i < typesArray.length; i++) {
for (prop in key) {
if (key[prop].type === typesArray[i].type) {
typesArray[i].image = key[prop].img;
}
}
}
It loops through the array ("typesArray"), and for each array item, it go through all the objects in key looking for the one with the same "type". When it finds it, it takes that key object's "img" and saves into the array.
Using lodash (https://lodash.com/):
var key = {
spawn:{type:1,img:app.assets.get('assets/spawn.svg')},
wall:{type:2,img:app.assets.get('assets/wall.svg')},
grass:{type:3,img:app.assets.get('assets/grass.svg')},
spike:{type:4,img:app.assets.get('assets/spike.svg')},
ground:{type:5,img:app.assets.get('assets/ground.svg')}
};
var initialList = [{type:1,image:null},{type:3,image:null},{type:2,image:null},{type:2,image:null},{type:5,image:null}];
var updatedList = _.transform(initialList, function(result, item) {
item.image = _.find(key, _.matchesProperty('type', item.type)).img;
result.push(item);
});
This will go over every item in the initialList, find the object that matched their type property in key and put it in the image property.
The end result will be in updatedList