Write key and value to JSON file based on the data provided using Cypress - json

I have to write the captured data from the application in JSON file as like below:
let expectedKey = 'PaperCode';
cy.get('app-screen').find('#code-details').invoke('val').as(code);
cy.get('#code').then(code) => {
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
if(expectedKey === 'StudentCode'){
appDetails.StudentCode = code;
}
if(expectedKey === 'DepartmentCode'){
appDetails.DepartmentCode = code;
}
if(expectedKey === 'PaperCode'){
appDetails.PaperCode = code;
}
if(expectedKey === 'ResultsCode'){
appDetails.ResultsCode = code;
}
})
})
Here, the key and its value are added to json in multiple if blocks. Still, there are many if blocks to implement based on different codes. I want to remove the if blocks and need to add the key and its value to json file based on the expectedKey. Any help please?

Using bracket notation appDetails[expectedKey] to define the new property,
let expectedKey = 'studentCode';
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
cy.get('app-screen').find('#code-details').invoke('val')
.then(code) => {
appDetails[expectedKey] = code; // add (or overwrite) new key
cy.writeFile('cypress/fixtures/applicationDetails.json', appDetails)
})
})
Clearing the file of all keys except the one you want
const expectedKey = 'studentCode';
const appDetails = {}
cy.get('app-screen').find('#code-details').invoke('val')
.then(code) => {
appDetails[expectedKey] = code; // add (or overwrite) new key
cy.writeFile('cypress/fixtures/applicationDetails.json', appDetails)
})

Related

CSV File object to JSON in Angular

On the front end of my app I wanted to parse some data related to a CSV they upload. Through the file upload tool, I first get a FileList object and then pull the 1 file out of it.
I want to turn it into a json object which I could then iterate. I was thinking to user csv-parser from node, but I dont see a way to leverage a File object stored in memory.
How Can I accomplish this?
At first I was doing:
let f = fileList.item(0);
let decoder = new window.TextDecoder('utf-8');
f.arrayBuffer().then( data => {
let _data = decoder.decode(data)
console.log("Dataset", data, _data)
});
And that was passing the array buffer, and decoding the string. While I Could write a generic tool which process this string data based on \n and ',' I wanted this to be a bit more easier to read.
I wanted to do something like:
let json = csvParser(f)
is there a way to user csv-parser from node, (3.0.0) or is there another tool i should leverage? I was thinking that levering modules based on the browser ( new window.TextDecoder(...) ) is poor form since it has the opportunity to fail.
Is there a tool that does this? im trying to create some sample data and given a File picked from an input type="file" i would want to have this be simple and straight forward.
This example below works, but i feel the window dependancy and a gut feeling makes me think this is naive.
const f : File = fileList.item(0)
console.log("[FOO] File", f)
let decoder = new window.TextDecoder('utf-8');
f.arrayBuffer().then( data => {
let _data = decoder.decode(data)
console.log("Dataset", data, _data)
let lines = _data.split("\n")
let headers = lines[0].split(',')
let results = []
for ( let i = 1; i < lines.length; i++) {
let line = lines[i]
let row = {}
line.split(",").forEach( (item, idx) => {
row[headers[idx]] = item;
})
results.push(row)
}
console.log("JSON ARRAY", results)
})
The issue i run when i stop and do: ng serve is that it does not like using the arrayBuffer function and accessing TextDecoder from window, since that thost functions/classes are not a part of File and window respectively during build.
Any thoughts?
This is what I ended up doing, given the file input being passed into this function:
updateTranscoders(project: Project, fileList: FileList, choice: string = 'replace') {
const f: File = fileList.item(0)
//Reads a File into a string.
function readToString(file) : Promise<any> {
const reader = new FileReader();
const future = new Promise( (resolve,reject) => {
reader.addEventListener("load", () => {
resolve(reader.result);
}, false)
reader.addEventListener("error", (event) => {
console.error("ERROR", event)
reject(event)
}, false)
reader.readAsText(file)
});
return future;
}
readToString(f).then( data => {
let lines = data.split("\n")
let headers = lines[0].split(',')
let results = []
for (let i = 1; i < lines.length; i++) {
let line = lines[i]
let row = {}
line.split(",").forEach((item, idx) => {
row[headers[idx]] = item;
})
results.push(row)
}
if (choice.toLowerCase() === 'replace'){
let rows = project.csvListContents.toJson().rows.filter( row => row.isDeployed)
rows.push( ...results)
project.csvListContents = CsvDataset.fromJson({ rows: rows })
}else if (choice.toLowerCase() === 'append') {
let r = project.csvListContents.toJson();
r.rows.push(...results);
project.csvListContents = CsvDataset.fromJson(r);
}else {
alert("Invalid option for Choice.")
}
this.saveProject(project)
})
}
Now the CHOICE portion of the code is where I have a binary option to do a hard replace on CSV contents or just append to it. I would then save the project accordingly. This is also understanding that the first row contains column headers.

Adding elements to Immutable List within forEach

I have an Immutable Map, where each key is an arbitrary category (these will be a List). I then have an array of strings that each category must have. If a specific category does not contain that string, it will be added to its respective List inside the Map.
const categories = new Map({
cat1: new List(['animal', 'color']),
cat2: new List(['animal']),
});
const missingKeys = new Map({
cat1: new List(),
cat2: new List(),
});
categories.keySeq().toArray().forEach((category) => {
const keys = categories.get(category).keySeq().toArray();
const requiredKeys = ['animal', 'color'];
// loop through required keys and push any key that is not found
// in the categories Map
requiredKeys.forEach((key) => {
if (keys.indexOf(key) === -1) {
// add missing keys to respective category of `missingKeys`
missingKeys.get(category).push(key)
}
});
});
I expect that after the loop completes, categories will be updated accordingly. However, when I try to console.log categories, the Map has not been updated.
You seem to be missing the whole "immutable" part of this library. The line
missingKeys.get(category).push(key)
does not mutate missingKeys or any of the lists it contains. It returns a new List with the missing key at the end.
If you want missingKeys to change, you need to reassign the variable:
missingKeys = missingKeys.update(category, keyList => keyList.push(key));
Don't forget to change that
As a side note, you're doing a lot of weird conversion with keySeq and toArray. What you have could be written more simply as:
const requiredKeys = ['animal', 'color'];
categories.forEach((keys, category) => {
requiredKeys.forEach(requiredKey => {
if (!keys.contains(key)) {
missingKeys = missingKeys.update(category, keyList => keyList.push(key));
}
});
});
Or the even more idiomatic functional way:
const requiredKeys = List(['animal', 'color']);
const missingKeys = categories.map(keys =>
requiredKeys.filter(key =>
!keys.contains(key)))

LocalStorage: get every value from each key

I want to go through every key and get the name value from each key.
This is how my LocalStorage looks like.
key: 3 Value:
{"name":"Kevin","country":"Canada","about":"Test","image":""}
key: 4 Value:
{"name":"Homer","country":"Canada","about":"Test","image":""}
I want to getboth of these names and add them to my array. I tried it with this method:
for(var key in localStorage){
let user = JSON.parse(localStorage.getItem(key));
this.users.push(user);
}
Error I get is:
SyntaxError: Unexpected token e in JSON at position 1
var keys = Object.keys(localStorage);
keys.forEach(key=>{
var json_str =localStorage.getItem(key)
try {
var abc = JSON.parse(json_str);
this.user = abc;
} catch (e) {
console.log(e)
}
})
when you say I want to getboth of these names, i don't get it but either way you can try something like:
var keys = Object.keys(localStorage);
for(var i=0;i<keys.length;i++){
var key = keys[i];
console.log(key, localStorage[key]);
//store here "both names" where you want them
//you can also access each element with localStorage[key].name, localStorage[key].country, etc.
}
This is a refinement of Robert's answer.
Just enumerate all of the values (The keys themselves do not matter) in localStorage that have a name property that is a string. Then return that array.
Based on your own answer, you likely have inconsistent mutable state as moving your temporary variable to instance scope should not impact your situation.
function getUsers() {
return Object.values(localStorage)
.map(json => {
try {
return JSON.parse(json);
}
catch (e) {
return undefined;
}
})
.filter((user?: any): user is {name: string} => user && typeof user.name === 'string');
}
const users = getUsers();
You can consider using my library ngx-store to deal with localStorage, sessionStorage, cookies and a bit more in Angular. To achieve what you want you will be able to store whole array in storage or just use code like the below with your current data structure:
import { LocalStorageService } from 'ngx-store';
export class Example {
public users: Array<any> = [];
constructor(public localStorageService: LocalStorageService) {
this.localStorageService.utility.forEach((value, key) => this.users.push(value));
}
}
Really, it can be just that simple ;)
You can use method hasOwnProperty('propertyName') to check name available or not in object. Then perform the operation that you want.
let localStorage = {
"key1": {"name":"Kevin","country":"Canada","about":"Test","image":""},
"key2": {"name":"Homer","country":"Canada","about":"Test","image":""}
}
for(let key of Object.keys(localStorage)){
if(localStorage[key].hasOwnProperty('name')){
console.log(localStorage[key]['name']);
}
}
const allItems = []
const keys = Object.keys(window.localStorage); // all keys
keys.forEach(key=> {
const item = JSON.parse(localStorage.getItem(key) + ''); //item with type Object
allItems.push(item);
});
console.log(allItems) // arry of object
I manage to solve it, was a simple error by always initializing a new let user inside the loop.
I moved out the user and the rest of the code works.
user: any;
getUsers():void{
for(var key in localStorage){
this.user = JSON.parse(localStorage.getItem(key));
this.users.push(this.user);
}
}

How to alter keys in immutable map?

I've a data structure like this (generated by normalizr):
const data = fromJS({
templates: {
"83E51B08-5F55-4FA2-A2A0-99744AE7AAD3":
{"uuid": "83E51B08-5F55-4FA2-A2A0-99744AE7AAD3", test: "bla"},
"F16FB07B-EF7C-440C-9C21-F331FCA93439":
{"uuid": "F16FB07B-EF7C-440C-9C21-F331FCA93439", test: "bla"}
}
})
Now I try to figure out how to replace the UUIDs in both the key and the value of the template entries. Basically how can I archive the following output:
const data = fromJS({
templates: {
"DBB0B4B0-565A-4066-88D3-3284803E0FD2":
{"uuid": "DBB0B4B0-565A-4066-88D3-3284803E0FD2", test: "bla"},
"D44FA349-048E-4006-A545-DBF49B1FA5AF":
{"uuid": "D44FA349-048E-4006-A545-DBF49B1FA5AF", test: "bla"}
}
})
A good candidate seems to me the .mapEntries() method, but I'm struggling on how to use it ...
// this don't work ... :-(
const result = data.mapEntries((k, v) => {
const newUUID = uuid.v4()
return (newUUID, v.set('uuid', newUUID))
})
Maybe someone can give me a hand here?
mapEntries is the correct method. From the documentation, the mapping function has the following signature:
mapper: (entry: [K, V], index: number, iter: this) => [KM, VM]
This means that the first argument is the entry passed in as an array of [key, value]. Similarly, the return value of the mapper function should be an array of the new key and the new value. So your mapper function needs to look like this:
([k, v]) => {
const newUUID = uuid.v4()
return [newUUID, v.set('uuid', newUUID)]
}
This is equivalent to the following (more explicit) function:
(entry) => {
const key = entry[0]; // note that key isn't actually used, so this isn't necessary
const value = entry[1];
const newUUID = uuid.v4()
return [newUUID, value.set('uuid', newUUID)]
}
One thing to note is that the templates are nested under the templates property, so you can't map data directly -- instead you'll want to use the update function.
data.update('templates', templates => template.mapEntries(...)))
So putting everything together, your solution should look like the following:
const result = data.update('templates', templates =>
templates.mapEntries(([k, v]) => {
const newUUID = uuid.v4()
return [newUUID, v.set('uuid', newUUID)]
})
);

Redux, Fetch and where to use .map

Consider this scenario:
app loads => fetches json from api => needs to modify json returned
In this case, I'm using moment to make some date modifications and do some grouping that I'll use in the UI. I looked on stack and found a similar question but didn't feel like it provided the clarity I am seeking.
Where should I use .map to create the new objects that contain the formatted & grouped dates? Should I manipulate the raw json in the api call or in the redux action before I dispatch? What is the best practice?
Is it OK to add properties and mutate the object as I am showing below,
service["mStartDate"] = mStartDate before I put the data into my store and treat it as immutable state?
First Approach - changing raw json in the api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
})
}
}
2nd Approach, make a simple api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
}
}
Change the json in the action before dispatching
export function getDataByID() {
return (dispatch, getState) => {
dispatch(dataLookupRequest())
const state = getState()
const groupedData = {}
return TicketRepository.getDataByID(userData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
return groupedData
})
.then(groupedData => {
dispatch(lookupSuccess(groupedData))
})
.catch(err => dispatch(dataLookupFailure(err.code, err.message)))
}
}
All data manipulation should be handled by your reducer. That is, the returned response data should be passed on to a reducer. This practice is common, because this way if there's a problem with your data, you will always know where to look - reducer. So neither of your approaches is "correct". Actions should just take some input and dispatch an object (no data manipulation).
When you want to manipulate data for 'view' purposes only, consider using reselect library, which makes it easier to handle "data views" that are composed of the existing data.