Best way to convert URL query params into a formatted array - javascript

I have a URL with query params that looks something like this
&rootDimension=availability%253AOnlinepipsymbBrand%253ADell%253ABICpipsymbProduct%2520Rating%254A3%2520stars%2520and%2520above
I am doing a decodeURIComponent(query.rootRootdimension).split("pipsymb") which returns an array that looks like this
["availability:Online", "Brand:Dell:BIC", "Product Rating:4 stars and above"]
I basically need to check the array and remove keys that aren't "Brand" or "Product Rating". So in this case it should return an array ["Brand:Dell:BIC", "Product Rating:4 stars and above"].
If the product rating is "4 stars and above" should replace it with "Top Rated" if not it should just keep the rating for example ["Brand:Dell:Bic", "Product Rating: "3 stars and above"]. The array should then look like this
["Brand:Dell:BIC", "Product Rating:Top Rated"].
The result I am looking for is ["Dell", "Bic", "Top Rated"]
I tried the function below and a few other things but I didn't get what I was looking. Thanks for the help/suggestions!
const getRefinements = (query) => {
decodeURIComponent(query.rootDimension).split("pipsymb").reduce((obj, str) => {
let strParts = str.split(/::|:/);
if (strParts[0] && strParts[1]) {
obj[strParts[0].replace(/\s+/g, "")] = strParts[1];
return Object.values(pick(obj, ["Brand", "ProductRating"]))
}
})
}

Try following:
let query = decodeURIComponent(
"&rootDimension=availability%253AOnlinepipsymbBrand%253ADell%253ABICpipsymbProduct%2520Rating%254A3%2520stars%2520and%2520above"
);
query = query
.replace(/%3A/g, ":")
.replace(/%20/g, " ")
.replace(/%4A/g, "J");
const productDetails = query.split("pipsymb");
let brandPart = productDetails
.find(item => item.match("Brand"))
.replace("Brand:", "")
.split(":");
let productRating = productDetails
.find(item => item.match("Product Rating"))
.split("J")[1];
if (productRating.includes("4")) {
productRating = "Top Rated";
}
const result = [...brandPart, productRating];
console.log(result);
Output
['Dell', 'BIC', '3 stars and above']

If you have control over the URL, you should split the things you want to get into &availability=Online&brand=Dell,BIC&productRating=4 stars and above
This was you'll be able to split it a bit better without having to remove extra strings, using something like the already provided URLSearchParams function.
let queryString = `&availability=Online&brand=Dell,BIC&productRating=4 stars and above`,
urlParams = new URLSearchParams(queryString),
results = {},
arr = [];
urlParams.forEach((v,k) => {
if (k === "productRating") {
if (v === "4 stars and above") {
results.rating = "Top Rated";
} else {
results.rating = v;
}
} else if (k === "brand") {
results.brands = v.split(","); // replace with ":" if delimiter cannot be changed
}
});
// this should be what you get
// results = {
// rating : "Top Rated",
// brands : ["Dell", "BIC"]
// }
// this will give you what you originally wanted, but it's not a very good data structure, use results instead if possible
arr = results.brands.concat(results.rating);

Related

I want to get multiple types as query parameters by using .split(',') in WHERE IN()

I want to get multiple types as query parameters by using type.split(',') in WHERE IN().
I have service.js and DAO.js files. And I tried this like below.
service.js
const productTypeSort = async (name, type, productSort, page) => {
const sort = orderBy(productSort);
const categoryType = getCategoryType(name, type);
return await productsDao.productTypeSort(categoryType, sort, page);
};
const getCategoryType = (name, type) => {
const filter = type.toString().split(',')
const FilterType = {
category: `WHERE c.name = "${name}"`,
categoryType: `WHERE c.name = "${name}" AND t.name IN ("${filter}")`,
};
if (name && type) {
return FilterType.categoryType;
} else if (name) {
return FilterType.category;
}
return '';
};
DAO.js
const productTypeSort = async (categoryType, sort, page) => {
const products = await myDataSource.query(
`SELECT
c.name AS category,
t.name AS type,
pr.name,
pr.description,
price_origin,
pr.created_at,
count(*) OVER() AS totalCount
FROM products pr
JOIN category c ON c.id = pr.category_id
JOIN product_types t ON t.id = pr.type_id
${categoryType}
GROUP BY pr.id
${sort}
LIMIT ?, 9`,
[(page - 1) * 9]
);
return products;
};
and the query I sent is as follows.
?name=abc&type=aaa,bbb&sort=review&page=1
But this returns an empty data column.
{
"data": []
}
What am I doing wrong? Maybe the location of .split(',') is wrong? I need help.
Your problem is that your filter array is ["aaa,bbb"] and that ends up making a query string which looks like:
WHERE t.name IN ("aaa,bbb")
What you need is to map each value in filter to a single-quote enclosed string, and remove the double-quotes from the query:
filter = type.split(',').map(s => `'${s}'`)
// ...
categoryType: `WHERE c.name = "${name}" AND t.name IN (${filter})`,
This will give you something that looks like:
WHERE t.name IN ('aaa','bbb')
which should give you your desired result.
You should take note of what #Andreas referred to in their comment; you should preferably be using prepared statements rather than injecting user input directly into your query. If someone sent type="); DROP table products --
you would get a nasty surprise.

How to access elements inside an array of objects in Javascript for a movieapp prototype

I've only shown the JS part of my code because the .html part only has one input box with name="my-input" inside a form.
What happens in this function is, if I enter 'Adventure' in my input box it gives me Edge of Tomorrow as the title of the movie and if I enter Romantic it gives me Lalaland likewise. Now what I want is, shown in the movieList1() function i.e. I have two objects with same genre 'Adventure' for example, then I would want both the movie title, 'Edge of Tomorrow' and 'Dark Force' to be shown in my empty list with id 'here'. I want title of all the movies to be shown if the genre is similar but I'm able to only get one movie title at a time that is shown. I'm new to JS and object and array of objects looks a little confusing. Any help would be very much appreciated.
function movieList(){
//This function is called in an 'onkeyup' event inside the input box.
var x = document.forms["my-form"]["my-input"].value; // x has what the user enters inside the
inputbox.
const objList = [{
title:'Edge of Tommorow',
Genre: 'Adventure',
},
{
title:'DarkForce',
Genre: 'Tactical',
},
{
title:'LalaLand',
Genre:'Romantic'
}];
objList.forEach((ele,index) => {
if(ele.Genre=='Adventure' && x==ele.Genre) {
var ans = ele.title;
document.getElementById('here').innerHTML = ans; // 'here' has the id of an empty <li></li> where
the title is shown.
}
else if(ele.Genre=='Tactical' && x==ele.Genre)
{
var ans = ele.title;
document.getElementById('here').innerHTML = ans;
}
else if(ele.Genre=='Romantic' && x==ele.Genre)
{
var ans = ele.title;
document.getElementById('here').innerHTML = ans;
}
else if(x=='')
{
document.getElementById('here').innerHTML='';
}
});
}
function movieList1(){
//This function is called in an 'onkeyup' event inside the input box.
var x = document.forms["my-form"]["my-input"].value; // x has what the user enters inside the input
box.
const objList = [{
title:'Edge of Tommorow',
Genre: 'Adventure',
},
{
title:'DarkForce',
Genre: 'Tactical, Adventure',
},
{
title:'LalaLand',
Genre:'Romantic,Tactical'
}];
objList.forEach((ele,index) => {
if(ele.Genre=='Adventure' && x==ele.Genre) {
var ans = ele.title;
document.getElementById('here').innerHTML = ans; // 'here' has the id of an empty <li></li> where
the title is shown.
}
else if(ele.Genre=='Tactical' && x==ele.Genre)
{
var ans = ele.title;
document.getElementById('here').innerHTML = ans;
}
else if(ele.Genre=='Romantic' && x==ele.Genre)
{
var ans = ele.title;
document.getElementById('here').innerHTML = ans;
}
else if(x=='')
{
document.getElementById('here').innerHTML='';
}
});
}
I'm guessing this is what you want to do.
const objList = [{
title: 'Edge of Tommorow',
Genre: 'Adventure',
},
{
title: 'DarkForce',
Genre: 'Tactical',
},
{
title: 'LalaLand',
Genre: 'Adventure'
}];
let Gen = "Adventure"; // suppose this is from your input
let temp = []; // create an array to store all similar genre title
objList.forEach(x => {
if (x.Genre == Gen) {
temp.push(x.title);
}
})
console.log(temp);
Output:
[ 'Edge of Tommorow', 'LalaLand' ]
you can also store objects in the temp array.
Thinking you are expecting the output like:: (Method movieList1)
for inputGenre=Tactical output will be DarkForce,LalaLand
for inputGenre=Adventure output will be Edge of Tommorow,DarkForce
document.getElementById("txtGenre").addEventListener("keyup",searchMovies)
function searchMovies(){
let inputGenre=document.getElementById("txtGenre").value;
const objList = [{
title:'Edge of Tommorow',
Genre: 'Adventure',
},
{
title:'DarkForce',
Genre: 'Tactical, Adventure',
},
{
title:'LalaLand',
Genre:'Romantic,Tactical'
}];
let filterdData =objList.filter(r=>r.Genre.includes(inputGenre));
console.log(filterdData);
document.getElementById("result").innerHTML=filterdData.map(r=>r.Genre).join(",");
}
#result{
background-color:green;
}
Genre input: <input type="text" id="txtGenre" onClick="searchMovies"/>
<br/>
Output Movies List:
<div id="result">
</div>
There reason you are getting only one movie name is, you are re-writing the HTML again with the latest movie that matched the criteria each time forEach runs. You need to adjust your variables and how you assign your result to HTML.
Lets keep ans out of the loop, so it doesn't get overwrittern. And write to html at the end, after you have filtered all per your input.
var ans = [];
objList.forEach((ele,index) => {
if(ele.Genre=='Adventure' && x==ele.Genre) {
ans.push(ele.title);
} else if(ele.Genre=='Tactical' && x==ele.Genre) {
ans.push(ele.title);
} else if(ele.Genre=='Romantic' && x==ele.Genre) {
ans.push(ele.title);
}
// We dont need this
// else if(x=='') {
// document.getElementById('here').innerHTML='';
// }
});
document.getElementById('here').innerHTML = ans.join(', ');
At last, you join the answer using the in-built array function.
Now if you notice, your if statements are pretty much the same except for the hardcoded genre. So you could further refactor it.
var ans = [];
var genre = 'Adventure'; // assign your genre from input box here.
objList.forEach((ele,index) => {
if (ele.Genre == genre && x==ele.Genre) {
ans.push(ele.title);
}
});
document.getElementById('here').innerHTML = ans.join(', ');
Now you would have a single if statement which is easy to understand and modify.

Implementing an add and remove function into my class

I have this code below that takes items of a PC and returns a quote, price etc. As of now, the only way that I can add or remove components is by directly modifying the array. Here is the code
class PriceCalc {
Motherboard = 520.99;
RAM = 250.4;
SSD = 500.8;
HDD = 400.66;
Case = 375.5;
Monitor = 600.75;
Keyboard = 100.99;
Mouse = 25.5;
constructor(Obj) {
this.parts = Obj;
this.cost = "$" + Obj.reduce((a, b) => a + this[b], 0).toFixed(2);
this.retail = "$" + (Obj.reduce((a, b) => a + this[b], 0) + Obj.reduce((a, b) => a + this[b], 0) * 1.75).toFixed(2);
this.quote = "Your quote is " + this.retail;
}
}
This is the key values
quote = new PriceCalc(["Motherboard", "RAM", "SSD", "HDD", "Case", "Monitor", "Keyboard", "Mouse"]);
console.log(quote.parts);
console.log(quote.cost);
console.log(quote.retail);
console.log(quote.quote);
What Id like to implement is a code as shown below that I can add within the class and call it to add new parts to the quote or remove existing parts.
function removePart(arr, part) {
return arr.filter(function (ele) {
return ele != part;
});
} var result = removePart(["Motherboard", "RAM", "SSD", "HDD", "Case", "Monitor", "Keyboard", "Mouse"], "Mouse");
console.log(result)
I would like to implement it in a way where I can call this.parts (as stated in the previous code) and either call a new function that pushes new parts to "parts" or call a new function that removes certain parts that are already in the list. I hope what I've described makes sense
Here is s a general example template of what I mean, without the functional code:
class quote {
constructor(/*...*/) {
this.parts = ....
}
add() {
//adds item
}
remove() {
//removes item
}
}
example = new quote(["part1", "part2"])
add(part3)
console.log(example) //returns ["part1" "part2" "part3"]
remove(part1)
console.log(example) //returns ["part2" "part3"]
This should work
class quote {
constructor(initialParts) {
this.parts = initialParts
}
add(part) {
this.parts = [...this.parts, part]
}
remove(part) {
this.parts = this.parts.filter( p => p !== part)
}
}
example = new quote(["part1", "part2"])
example.add('part3')
console.log(example) //returns ["part1" "part2" "part3"]
example.remove('part1')
console.log(example) //returns ["part2" "part3"]
If you want multiple of the same item you can still use an object and store the key as the name of the product and the number of items as the value. simply adjust the functions to account for this.

How to keep array values in the same column when converting to CSV

I'm trying to convert Object values to CSV but join method separates my array values into different columns in Excel. Any idea on how can I avoid this?
The function:
window.downloadCsv = function(records) {
console.log(records);
const array = [Object.keys(records)].concat(records);
console.log(array);
let result = array.map(it => {
let objectValues = Object.values(it);
for (let i = 0; i < objectValues.length; i++) {
if (Array.isArray(objectValues[i])) {
//Keep it as array
}
}
return objectValues;
}).join('\n');
console.log(result);
let hiddenElement = document.createElement('a');
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(result);
hiddenElement.target = '_blank';
hiddenElement.download = 'records.csv';
hiddenElement.click();
};
Possible records input:
id: "5e8468e2db05ff589ca61b30"
title: "Example Application Number 1"
status: "Preparing Documents"
principalInvestigator: "Mr. Harry Styles"
coInvestigators: ["Niall Horan, Liam Payne, Zayn Malik, Louis Tomilson"]
partners: null
funder: "EPSRC Standard research"
researchGroup: "MedEng"
scheme: "Travel Grant"
requestedAmount: null
estimatedAmount: 1234
submissionDate: "2020-03-23T00:00:00.000+01:00"
startDate: "2020-03-29T00:00:00.000+01:00"
estimatedDuration: null
endDate: null
facility: null
comments: null
dateCreated: "2020-04-01T12:11:46.783+02:00"
lastUpdated: "2020-04-01T12:11:46.783+02:00"
dateDeleted: null
__proto__: Object
Current output of result:
id,title,status,principalInvestigator,coInvestigators,partners,funder,researchGroup,scheme
5e8468e2db05ff589ca61b30,Example Application Number 1,Preparing Documents,Mr. Harry Styles,Niall Horan, Liam Payne, Zayn Malik, Louis Tomilson
Desired output:
id,title,status,principalInvestigator,coInvestigators,partners,funder,researchGroup,scheme
5e8468e2db05ff589ca61b30,Example Application Number 1,Preparing Documents,Mr. Harry Styles,[Niall Horan, Liam Payne, Zayn Malik, Louis Tomilson],Apple,Microsoft,XresearchGroup,YScheme
It is may easier to understand it in Excel format.
Currently, it looks like this after exporting: https://imgur.com/2I8h9kl
And the desired look would be: https://imgur.com/bFUKQY2
So, pretty much I would like to keep array values in the same column rather than separating them into different ones which shifts all other columns as well in the CSV.
Try to always set up a minimal reproducible example (https://stackoverflow.com/help/minimal-reproducible-example)
Here is mine:
The most important part is this:
const headers = Object.keys(records).join(',')
const values = Object.values(records).map(child => {
if (child instanceof Array){
//return child.join('; ');
const str = JSON.stringify(child).replace(/"/g, "'");
return `"${str}"`;
}else{
return child;
}
}).join(',')
const result = [headers, values].join('\n')
Where we take the keys and the value and put them each in an array and then put them in one array and join them with a new line [headers, values].join('\n')
Inside the map you can do either this:
const values = Object.values(records).map(child => {
if (child instanceof Array){
const str = JSON.stringify(child).replace(/"/g, "'");
return `"${str}"`;
}else{
return child;
}
}).join(',')
Which makes the array string show up in Excel like this:
"['Niall Horan','Liam Payne','Zayn Malik','Louis Tomilson']"
Or you can do the map like this:
const values = Object.values(records).map(child => {
if (child instanceof Array){
return child.join('; ');
}else{
return child;
}
}).join(',')
And then the output in Excel is like this (semicolon is not read as a column separator unless you use that locale - i.e. German locale):
"Niall Horan; Liam Payne; Zayn Malik; Louis Tomilson"
const recordObj = {
id: "5e8468e2db05ff589ca61b30",
title: "Example Application Number 1",
status: "Preparing Documents",
principalInvestigator: "Mr. Harry Styles",
coInvestigators: ["Niall Horan", "Liam Payne", "Zayn Malik", "Louis Tomilson"],
partners: null,
funder: "EPSRC Standard research",
researchGroup: "MedEng",
scheme: "Travel Grant",
requestedAmount: null,
estimatedAmount: 1234,
submissionDate: "2020-03-23T00:00:00.000+01:00",
startDate: "2020-03-29T00:00:00.000+01:00",
estimatedDuration: null,
endDate: null,
facility: null,
comments: null,
dateCreated: "2020-04-01T12:11:46.783+02:00",
lastUpdated: "2020-04-01T12:11:46.783+02:00",
dateDeleted: null
}
downloadCsv(recordObj)
function downloadCsv(records) {
//console.log(records);
const headers = Object.keys(records).join(',')
const values = Object.values(records).map(child => {
if (child instanceof Array){
//return child.join('; ');
const str = JSON.stringify(child).replace(/"/g, "'");
return `"${str}"`;
}else{
return child;
}
}).join(',')
const result = [headers, values].join('\n')
//console.log(headers);
//console.log(values);
console.log(result);
let hiddenElement = document.createElement('a');
hiddenElement.href = 'data:text/csv;charset=utf-8,' + encodeURI(result);
hiddenElement.target = '_blank';
hiddenElement.download = 'records.csv';
hiddenElement.click();
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try to convert an array to string and then replace commas with another separator, semicolon for example - this way csv interpretator won't split values in array and will count it as single item
let records = {id: "5e8468e2db05ff589ca61b30",
title: "Example Application Number 1",
status: "Preparing Documents",
principalInvestigator: "Mr. Harry Styles",
coInvestigators: ["Niall Horan, Liam Payne, Zayn Malik, Louis Tomilson"]}
const array = [Object.keys(records)].concat(records);
let result = array.map(it => {
let objectValues = Object.values(it);
for (let i = 0; i < objectValues.length; i++) {
if (Array.isArray(objectValues[i])) {
objectValues[i]= objectValues[i]=(`[${objectValues[i].join(';')}]`).replace(/,/g, ';')
}
}
return objectValues;
}).join('\n');
console.log(result);
I would personally suggest changing your solution a bit. You don't need to concatenate keys and values, it makes the .map() unnecessarily complicated. Here's what I would do:
var keys = Object.keys(records);
// we take the array of values, but we transform any array
// in a string of values separated by +
var values = Object.values(records).map(record => {
if (Array.isArray(record))
return record.join("+");
return record;
});
// Now we can just put everything together
var result = keys.join(",") + "\n" + values.join(",");
console.log(result);

javascript String to associative object

I am just wondering if there is an easy way to create an associative object from a string that needs double split, thisstring is the result from an api call, so length of the object could change.
For instance, if I have a string that looks like this:
var infoValue = 'Loan Date~Loan Number~Loan Amount|15/03/2016~1042~620|15/03/2016~1044~372';
I want to have an object that looks like this:
[
{
"Loan Date":"15/03/2016",
"Loan Number":"1042",
"Loan Amount":"620",
},
{
"Loan Date":"15/03/2016",
"Loan Number":"1042",
"Loan Amount":"620",
}
]
What I am doing right now is something like
var res = infoValue.split("|");
var activeLoans = new Array();
for(field in res) {
if(res[field] != ''){
activeLoans.push(res[field]);
}
}
for(field in activeLoans){
var row = activeLoans[field];
rowSplit = row.split("~");
}
But I am not happy with this approach, as I need to create a table to display this data, and the site that I am getting this api might change the order of the response of the string, or might add other values
What you have done is about all you can do, though I would not use for..in for a typical array. You should be able to deal with any sequence of values, as long as the header is consistent with the rest of the data, e.g.
var infoValue = 'Loan Date~Loan Number~Loan Amount|15/03/2016~1042~620|15/03/2016~1044~372';
function parseInfoValue(s) {
var b = s.split('|');
var header = b.shift().split('~');
return b.reduce(function(acc, data) {
var c = data.split('~');
var obj = {};
c.forEach(function(value, i){
obj[header[i]] = value;
})
acc.push(obj);
return acc;
},[]);
}
var x = parseInfoValue(infoValue);
document.write(JSON.stringify(x));
This will create the required structure no matter how many items are in each record, it just needs a label for each item in the header part and a value for each item (perhaps empty) in every data part.
Edit
Thinking on it a bit more, I don't know why I used forEach internally when reduce is the obvious candidate:
var infoValue = 'Loan Date~Loan Number~Loan Amount|15/03/2016~1042~620|15/03/2016~1044~372';
function parseInfoValue(s) {
var b = s.split('|');
var header = b.shift().split('~');
return b.reduce(function(acc, data) {
acc.push(data.split('~').reduce(function(obj, value, i) {
obj[header[i]] = value;
return obj;
}, {}));
return acc;
}, []);
}
var x = parseInfoValue(infoValue);
document.write(JSON.stringify(x));

Categories

Resources