SheetJS specify header order with json_to_sheet - javascript

I am using SheetJS in angular to export json as .xlsx file. For reference the json could be as follows:
[{
"ID": "E111",
"Name": "John",
"LastLogin": "2022-02-12"
},
{
"ID": "E112",
"Name": "Jake",
"Score": 22
"LastLogin": "2022-02-12"
}]
Note: The keys to the object are unknown, and can vary. The only known keys are ID and LastLogin.
I am using the following function to export
public exportAsExcelFile(json: any[], excelFileName: string): void {
const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(json);
console.log('worksheet',worksheet);
const workbook: XLSX.WorkBook = { Sheets: { 'data': worksheet }, SheetNames: ['data'] };
const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
this.saveAsExcelFile(excelBuffer, excelFileName);
}
private saveAsExcelFile(buffer: any, fileName: string): void {
const data: Blob = new Blob([buffer], {
type: EXCEL_TYPE
});
FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION);
}
The resulting excel looks like this
I want LastLogin to be the last column no matter the object. Is there a way to achieve this? I am pretty new to this, so any help is appreciated.

The behaviour of SheetJS here is to take the order of column headers for the Excel data from the first row, and then as new object keys are encountered then for the matching row header to be added at the end.
To control this behaviour to get the output formatted the way you want, you can process the input json before calling XLSX.utils.json_to_sheet.
Define this function:
function restructureObjectForSheet(obj) {
// for each object in the array put the keys in a new array
// flatten that array
// there will be duplicate names which can be removed with Set
// turn it back into an array
const uniqKeys = Array.from(new Set(obj.map(o => Object.keys(o)).flat()));
// remove LastLogin from this array
// then put LastLogin at the end of the array
const endKey = "LastLogin";
const rowHeaders = uniqKeys.filter(k => k !== endKey).concat(endKey);
// process the original data into a new array
// first entry will define row headers in Excel sheet
const newData = obj.map(o => {
return rowHeaders.reduce((a, c) => {a[c] = o[c] ; return a}, {});
});
return newData;
}
I've commented the code, but the essential features are:
get an array of all the unique keys across all the objects in your input array (your json variable)
ensure LastLogin is the last element of the array
create one new object per input object and where the original data does not have the property (e.g. Score) then the value is undefined
Now, in your exportAsExcelFile method, just make this adjustment before the 1st line:
const newJson = restructureObjectForSheet(json);
const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(newJson );

Related

How to parse json if Key'name dynamicly changes each time node js

I receive JSON data from the service, but the keys change in the data with each request, below I will give an example in three versions.
Exmaple 1:
{
"trackingPayloads": {
"Rltyn4gLRIWRKj9kS0YpWXytG81GZwcPWjEE7f31ALlq": "{"title":"Red Shoes","index":3,"id":"17777","type":"category"}',
"ywtA6OyM0hzVZZvnUjxoxJDI1Er9ArfNr8XKyi1D5Zzk": "{"title":"White Shoes","index":3,"id":"17777","type":"category"}',
}
}
Example 2:
{
"trackingPayloads": {
"36tW7DqZ3H9KKBEAumZmowmUwmDRmVCjQgv5zi9GM3Kz": "{"title":"Red Shoes","index":3,"id":"17777","type":"category"}',
"OgtE51n3YtvrVXWLFjPmpnRt2k5DExF7ovxmBTZrZ6wV": "{"title":"White Shoes","index":3,"id":"17777","type":"category"}',
}
}
Example 3:
{
"trackingPayloads": {
"k2toY29glt2JEp9Wi1X5M7ocno0E0mS4JQVyDuGyQ2rM": "{"title":"Red Shoes","index":3,"id":"17777","type":"category"}'",
"5ef2ec3c3573eebecc9690b69619ec7b9c93b609": "{"title":"White Shoes","index":3,"id":"17777","type":"category"}',
}
}
As you can see, the data included in the keys does not change since I am requesting the same information, but the key will change with each request.
Please help, what are the options to get the data Title, Index and any other content in these keys using node js?
Only one option came to my mind - to rename the keys upon receipt in 1,2,3 ... and then get data from them, but this needs to be done dynamically, since about 120 requests per minute are made, you need to get this data quickly, there are no options to save it to a file (I didn’t understand how)
UPDATE, added my code.
I am attaching an example of my code, the idea is to eventually get the data I need from the right keys from trackingPayloads, please help with the code <3
const AwaitAPIResponse = await ProductAPI(product_sku);
const $ = cheerio.load(AwaitAPIResponse);
const JSONDATA = [];
$('pre').each(function() {
JSONDATA.push($(this).text());
});
const ProductJson = JSON.parse(JSONDATA[0]) // this is where I get all the data
const MainJson = ProductJson["trackingPayloads"] // here I go to the trackingPayloads you saw above
How can I get the data I need?
You can use Object.keys() to get all the different keys of an object and use a loop to go through them.
Therefore, you can rework this code in such a way that each of the values is stored as an element in an array, maybe makes the data easier to work with:
const convert = object => {
const ret = []
for (const key of Object.keys(object)) {
ret.push(object[key])
}
return ret
}
This will give you following result for your use case:
[{"title":"Red Shoes","index":3,"id":"17777","type":"category"},
{"title":"Red Shoes","index":3,"id":"17777","type":"category"}]
The way you'd call this is as follows:
const some_parsed_json = {
"k2toY29glt2JEp9Wi1X5M7ocno0E0mS4JQVyDuGyQ2rM": {
title:"Red Shoes",
index:3,
id:"17777",
type:"category"
},
"5ef2ec3c3573eebecc9690b69619ec7b9c93b609": {
title:"Red Shoes",
index:3,
id:"17777",
type:"category"
}
}
const json_object_values = convertor(some_parsed_json)
If you don't car about the key you could use Object.values on the received object to get the values
Object.values(payload)
// With your example it will return:
// [{"title":"Red Shoes","index":3,"id":"17777","type":"category"},
// {"title":"Red Shoes","index":3,"id":"17777","type":"category"}]
or in a more complete example
async function getParsedValues() {
const responseString = await service.getData(); // '{"trackingPayloads":{"Rltyn4gLRIWRKj9kS0YpWXytG81GZwcPWjEE7f31ALlq":{"title":"Red Shoes","index":3,"id":"17777","type":"category"},"ywtA6OyM0hzVZZvnUjxoxJDI1Er9ArfNr8XKyi1D5Zzk":{"title":"White Shoes","index":3,"id":"17777","type":"category"}}}'
const parsedResponse = JSON.parse(responseString); // { trackingPayloads: { Rltyn4gLRIWRKj9kS0YpWXytG81GZwcPWjEE7f31ALlq: { title:'RedShoes', index: 3, id: '17777', type: 'category' }, ywtA6OyM0hzVZZvnUjxoxJDI1Er9ArfNr8XKyi1D5Zzk:{title:'WhiteShoes', index: 3, id: '17777', type: 'category' } }}
const values = Object.values(parsedResponse); // [{"title":"Red Shoes","index":3,"id":"17777","type":"category"}, {title:'WhiteShoes', index: 3, id: '17777', type: 'category' }]
return values;
}

How do I convert a .csv file to a javascript array of dictionaries. Keys of each dictionary are column headers in the .csv file and items are values

For example, if I had a .csv file where the column headers are mentioned in the first row of the file and their subsequent values are specified in the following rows like so,
index,id,description,component,service
0,5,lorem ipsum,7326985,Field Service
The first step is to separate out the data and the column headers. I'm going to make the assumption that the csv is stored in your program as an array of strings, each representing a line, i.e.
const csv = [
"index,id,description,component,service",
"0,5,lorem ipsum,7326985,Field Service"
]
If your CSV isn't already represented this way, you can achieve this with the fs module:
const fs = require('fs');
fs.readFile('./data.csv', (err, data) => {
if (err) {
throw new Error(err);
}
const csv = String(data) // convert the buffer to a string
.split('\n') // Split the string into an array where each item contains one line
.filter(Boolean); // Remove any empty lines
// Do the rest of the operations on the CSV data here
});
In which case we can split them up easily using the spread operator, after splitting each string on the commas:
const [ headers, ...data ] = csv.map(row => row.split(','));
Now our objects look something like this:
// headers
[ 'index', 'id', 'description', 'component', 'service' ]
// data
[
[ '0', '5', 'lorem ipsum', '7326985', 'Field Service' ]
]
And we can now go ahead and map each of the arrays in the data array to an object, using each value's index to map it to a specific heading from the CSV.
data.map(row => {
const rowObject = {};
row.forEach((value, index) => {
rowObject[headers[index]] = value;
});
return rowObject
});
We of course have to return this mapped data object somewhere, or assign it to a new variable as the Array.map() function does not update the original array but creates a new one. Putting it all together into a code snippet it could look like this:
const csv = [
"index,id,description,component,service",
"0,5,lorem ipsum,7326985,Field Service"
];
function csvToJSON(csv) {
const [headers, ...data] = csv.map(row => row.split(','));
return data.map(row => {
const rowObject = {};
row.forEach((value, index) => {
rowObject[headers[index]] = value;
});
return rowObject
});
}
console.log(csvToJSON(csv));

Re-ordering of JSON data

I am new to JSON and want to re-structure my JSON response,
JSON-structure now-
{
"map": {
"Cityname": "[Jammu, Srinagar]",
"Pincode": "[180001, 190001]"
}
}
How I need it to be-
[
{ Cityname: "Jammu", Pincode: 180001},
{ Cityname: "Srinagar", Pincode: 190001}
]
Is there a way to do so, I searched for some possible solutions but was unable to do so.
Here is a Dynamic way of doing so, should work with any json string with the same layout. I fixed some of your json string.
// first we use JSON.parse() to turn a json string into a JS Object.
const data = JSON.parse(`{ "map": { "cityName": ["Jammu", "Srinagar"], "pinCode": [180001, 190001]}}`);
// cities will hold our city objects
const cities = [];
// Object.entries(theObject) returns theObjects keys and values as an array
// EG:
// [[key, value], [key, value]]
// forEach() loops through each key, value pair.
// Because we know that we are going to get a key value pair,
// I destructured the array into it's key and values using:
// [key, values] = [properties];
Object.entries(data.map).forEach((prop) => {
const [key, values] = prop;
values.forEach((value, index) => {
// now we check that the current index is an object.
// we do this because we can't add a property and value otherwise.
if(typeof cities[index] != "object"){
cities[index] = {};
}
// now we set the current value
cities[index][key] = value;
})
})
console.log(cities);
Your JSON response, its not quite logical because there is not mapping between city and pincode. I assumed that cityname and pincode are in the same order in the arrays. I used exact json structure you provided in the question.
You can skip additional steps substring and parse if your json data have correct data types (Cityname string array / Pincode int array).
const json = {
"map": {
"Cityname": "[Jammu, Srinagar]",
"Pincode": "[180001, 190001]"
}
}
const someFunc = () => {
let output = [];
const {Cityname, Pincode} = json.map;
const citynameArray = (Cityname.substring(1, Cityname.length-1)).split(",");
const pincodeArray = JSON.parse(Pincode);
citynameArray.map((v,i) => {
output.push({Cityname: v, Pincode: pincodeArray[i]})
});
return output;
}
console.log(someFunc());

Alternative to eval for converting a string to an object

I have a function that is using eval to convert a string with an expression to an object based on the parameter.
let indexType = ["Mac", "User", "Line", "Mask", "Ip", "Location"]
const filterIndex = (item) => {
filteredIndexSearch = []
eval(`search${item}`).forEach((e) => filteredIndexSearch.push(searchData[e.key]))
}
filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))])
searchData is an array that returns values based on the user input.
searchTotal is an array with the length of each search{item} array.
The filterIndex function takes the highest value from the searchData array and corresponds it to the indexType array, then use eval to convert the string to an object to pass the value to the filteredIndexSearch array.
What would be a better alternative to eval?
EDIT
To add more information on what this does:
searchData = [
[
{
key: 1,
data: "0123456789101"
},
{
key: 1,
data: "John Smith"
}
],
[
{
key: 2,
data: "0123456789102"
},
{
key: 2,
data: "Jane Smith"
},
]
]
const search = (data, key, container) => {
if (!data) data = "";
if (data.toLowerCase().includes(string)) {
container = container[container.length] = {
key: key,
data: data
}
}
}
const returnSearch = () => {
for (let i = 0; i < searchData.length; i++) {
search(searchData[i][0].data, searchData[i][0].key, searchMac)
search(searchData[i][1].data, searchData[i][1].key, searchUser)
}
}
returnSearch()
The data is incomplete, but hopefully conveys what I'm trying to do.
search will take the user input, and store the information in the corresponding array. If I input "Jo", it will return the searchUser array with only the "John Smith" value and all the other values with the same key. Inputting "102" returns the searchMac with the "0123456789102" value and all other values with the same key.
At the end of the day. I just want to convert search${parameter} to an object without using eval.
Move your global arrays into an object.
Somewhere it appears that you're defining the arrays, something like:
searchMac = [...];
searchUser = [...];
...
Instead of defining them as individual arrays, I'd define them as properties in an object:
searchIndices.Mac = [...];
searchIndices.User = [...];
...
Then, instead of using eval, your can replace your eval().forEach with searchIndices[item].forEach.
If the order of your search isn't important, your can instead loop through the keys of searchIndices:
Object.keys(searchIndices).forEach(item => {
searchIndices[item].forEach(...);
});
This ensures that if you ever add or drop an entry in searchIndices, you won't miss it or accidentally error out on an undefined search index.
Any time you have a situation with variables named x0, x1 etc, that should be a red flag to tell you you should be using an array instead. Variable names should never be semantically meaningful - that is code should never rely on the name of a variable to determine how the code behaves. Convert search0 etc into an array of search terms. Then use:
const filterIndex = (item) => search[item].map(i => searchData[i.key]);
filteredIndexSearch = filterIndex(indexType[searchTotal.indexOf(Math.max(...searchTotal))]);
(simplifying your code). Note that in your code, filteredIndexSearch is modified inside the arrow function. Better to have it return the result as above.

React object property value being duplicated on .push inside loop

I have a handleCreate function that takes care of taking some user data and inserting it into a database.
Inside the aliasArr.forEach() loop I POST into my DB new user instances for each element in the aliasArr array. This particular code works as expected, if I check the DB, I will find the new members.
After saving the new members, I want to keep the members in the members array so I can pass it along to another function.
For this, I'm doing members.push(memberAttributes); but if I log the contents of members I get the right amount of elements but the alias property value is duplicated (all other properties should have the same value cause they are being added into the same role in a batch operation).
If I have two new users, say: xyz and abc, I get:
[
{alias: "abc", Role: "admin", "grantedBy": "someone"},
{alias: "abc", Role: "admin", "grantedBy": "someone"},
]
Instead of:
[
{alias: "xyz", Role: "admin", "grantedBy": "someone"},
{alias: "abc", Role: "admin", "grantedBy": "someone"},
]
Here's the code:
handleCreate = () => {
const { memberAttributes } = this.state;
const { role, createRoleMember } = this.props;
const roleArr = [];
roleArr.push(role);
const aliasArr = memberAttributes.alias.split(",");
let members = [];
//false hardcoded during debugging.
if (false /* await aliasIsAdmin(memberAttributes.alias, roleArr) */) {
this.setState({ userExists: true });
} else {
memberAttributes["Granted By"] = window.alias;
memberAttributes.Role = role;
memberAttributes.timestamp = Date.now().toString();
this.handleClose();
aliasArr.forEach((currAlias) => {
memberAttributes.alias = currAlias;
console.log("memberAttributes:", memberAttributes);
members.push(memberAttributes);
console.log("members", members);
const marshalledObj = AWS.DynamoDB.Converter.marshall(memberAttributes);
const params = {
TableName: "xxx",
Item: marshalledObj,
};
axios.post(
"https://xxx.execute-api.us-west-2.amazonaws.com/xxx/xxx",
params
);
});
}
createRoleMember(members); //passing members to this function to do more stuff.
};
I'm wondering if this issue is due to memberAttributes being part of the component's state.
The problem here is that you are pushing references to the same object into the array after changing a value within that object. So whenever you make the change to memberAttributes.alias, it's changing the alias to the most recent one. After that, all references to the same object (which in this case is every item in the members array) present the new value in alias.
const obj = { alias: 'abc', role: 'role1' }
const arr = []
arr.push(obj)
obj.alias = 'new alias'
arr.push(obj)
for (var mem of arr) {
console.log(mem)
}
To fix it, you need to create a new object each time and push it onto the array instead, like so:
aliasArr.forEach((currAlias) => {
// Creates a new object in memory with the same values, but overwrites alias
const newMemberAttributes = Object.assign(memberAttributes, { alias: currAlias });
console.log("memberAttributes:", newMemberAttributes);
members.push(newMemberAttributes);
console.log("members", members);
...
}
Similarly, you can use the spread operator to create a deep copy of the object and then reassign alias.
aliasArr.forEach((currAlias) => {
// Creates a new object in memory with the same values, but overwrites alias
const newMemberAttributes = { ...memberAttributes };
newMemberAttributes.alias = currAlias
console.log("memberAttributes:", newMemberAttributes);
members.push(newMemberAttributes);
console.log("members", members);
...
}

Categories

Resources