Best way to remove empty query strings from an API request - javascript

In my react app I am tasked with sending multiple GET requests, sometimes with some optional filter params attached. Additionally I'm storing the params in my redux store so that they are able to stack on subsequent requests. Here's the problem: There are times when I'll clear the values of a param but since their keys are still saved to my store I'll get requests sent out like this:
/restaurants/?search=&state_id=2
Notice how the search param value is empty (as expected). I was wondering what would be the best approach in removing these type of lingering params? I looked into lodash's _.omitBy combined with _.isEmpty but this throws a sort of false positive in dealing with numeric values like pagination.
_.omitBy(params, ._isEmpty);
I didn't think that providing how exactly the params are via react/redux was necessary stored but am happy to provide it if it helps.
Note: I'm using axios and passing my params in as an object as opposed to a string:
axios.get('restaurants', {
params
});

Considering your params are stored in an object (as you mentioned in your latest edit), you can just remove properties containing empty strings:
const params = {
search: "",
state_id: 2
};
for (const key of Object.keys(params)) {
if (params[key] === "") {
delete params[key];
}
}
console.info(params);
This alternative has the advantage of being short and easy to maintain.
But for those with a string containing all params already serialized, it's easy to do using regular expressions:
function removeEmptyParams(query) {
return query.replace(/[^=&]+=(?:&|$)/g, "");
}
const testQuery = "f=1&search=&state_id=2&foo=&bar=12";
console.info(removeEmptyParams(testQuery));
Also very simple and easy to maintain.

The best and easiest way is to use a Spread Object literal on every optional param,
like this
params:{
...( CONDITION && {param_key: param_value}),
...( this.state_id != null && {state_id: this.state_id})
}
If condition is TRUE, the encapsulated key:value pair will be unwrapped, if condition is false , there will be no empty value or key.

You might need to parse that manually. Split on the & then split on the = and check if there's a value based on the length of the split - remove accordingly from there. If you're comfortable with regex you can check if there are values after the = instead of splitting.

This solution is a little crude, but it works. Let's assume your query params are stored in a Map, something like:
const params = {
foo: 'bar',
bang: 0,
buzz: '' // this value has been removed from your store
}
You could create an array of keys from your params Map, filter out the items that don't have a value and then join them all back together as query params.
const queryString = Object.keys(params).map(filter => {
if (params[filter] || Number.isInteger(params[filter])) {
return `${filter}=${params[filter]}`;
}
return null;
}).filter(item => item).join('&');
Which would result in foo=bar&bang=0

Related

Is there a better way that to Cast mongoose.Types.ObjectID references to String in order to compare them with == in some cases? [duplicate]

I have a node.js application that pulls some data and sticks it into an object, like this:
var results = new Object();
User.findOne(query, function(err, u) {
results.userId = u._id;
}
When I do an if/then based on that stored ID, the comparison is never true:
if (results.userId == AnotherMongoDocument._id) {
console.log('This is never true');
}
When I do a console.log of the two id's, they match exactly:
User id: 4fc67871349bb7bf6a000002 AnotherMongoDocument id: 4fc67871349bb7bf6a000002
I am assuming this is some kind of datatype problem, but I'm not sure how to convert results.userId to a datatype that will result in the above comparison being true and my outsourced brain (aka Google) has been unable to help.
Mongoose uses the mongodb-native driver, which uses the custom ObjectID type. You can compare ObjectIDs with the .equals() method. With your example, results.userId.equals(AnotherMongoDocument._id). The ObjectID type also has a toString() method, if you wish to store a stringified version of the ObjectID in JSON format, or a cookie.
If you use ObjectID = require("mongodb").ObjectID (requires the mongodb-native library) you can check if results.userId is a valid identifier with results.userId instanceof ObjectID.
Etc.
ObjectIDs are objects so if you just compare them with == you're comparing their references. If you want to compare their values you need to use the ObjectID.equals method:
if (results.userId.equals(AnotherMongoDocument._id)) {
...
}
converting object id to string(using toString() method) will do the job.
The three possible solutions suggested here have different use cases.
Use .equals when comparing ObjectId on two mongoDocuments like this
results.userId.equals(AnotherMongoDocument._id)
Use .toString() when comparing a string representation of ObjectId to an ObjectId of a mongoDocument. like this
results.userId === AnotherMongoDocument._id.toString()
According to the above,i found three ways to solve the problem.
AnotherMongoDocument._id.toString()
JSON.stringify(AnotherMongoDocument._id)
results.userId.equals(AnotherMongoDocument._id)
The accepted answers really limit what you can do with your code. For example, you would not be able to search an array of Object Ids by using the equals method. Instead, it would make more sense to always cast to string and compare the keys.
Here's an example answer in case if you need to use indexOf() to check within an array of references for a specific id. assume query is a query you are executing, assume someModel is a mongo model for the id you are looking for, and finally assume results.idList is the field you are looking for your object id in.
query.exec(function(err,results){
var array = results.idList.map(function(v){ return v.toString(); });
var exists = array.indexOf(someModel._id.toString()) >= 0;
console.log(exists);
});
I faced exactly the same problem and i simply resolved the issue with the help of JSON.stringify() as follow:-
if (JSON.stringify(results.userId) === JSON.stringify(AnotherMongoDocument._id)) {
console.log('This is never true');
}
Mongoose from 5 to 6 migration guide:
"Mongoose now adds a valueOf() function to ObjectIds. This means you can now use == to compare an ObjectId against a string."
https://mongoosejs.com/docs/migrating_to_6.html#objectid-valueof
Here is an example that explains the issue and why it confusing for many. Only the first console log shows the object in its true form, and any other debuging/loging will be confusing because they look the same.
// Constructor for an object that has 'val' and some other stuffs
// related to to librery...
function ID(_val) {
this.val = _val;
this.otherStuff = "other stuffs goes here";
}
// function to help user get usefull infos from the Object
ID.prototype.toString = function toString() {
return `${this.val}`;
};
// Create new Object of type ID
const id = new ID('1234567');
console.log("my ID: ", id); // my ID: Object {
// val: "1234567",
// otherStuff: "other stuffs goes here"
// }
console.log("my ID: " + id); // my ID: 1234567
console.log(id === '1234567'); // false
console.log(id == '1234567'); // true
console.log(id.toString() === '1234567'); //true
console.log(`${id}` === '1234567'); // true
console.log(new ID('1234567') === id); // false

Firestore create compound query from array

I'm trying to create a compound query to do a range query.
let query;
category.queryParams.query.fieldFilters.forEach(function(fieldFilter) {
query = collection.where(fieldFilter.field, filterOperationString(fieldFilter.filterType), fieldFilter.filterValue);
});
But it's not giving me the results I want.
I'm having a feeling that the query needs to be created chained like collection.where().where()
If that is the case, how can I transform the contents of my array using that syntax?
From my understanding, you have an array named fieldFilters that contains multiple objects with a field attribute (e.g. 'price'), a filterType attribute (e.g. '>') and a filterValue attribute (e.g. 50). If you are trying to do multiple range queries on different fields, then this will not work as stated in the documentation https://firebase.google.com/docs/firestore/query-data/queries#compound_queries. Solutions do exist for this problem, I suggest you read Frank van Puffelen response to the following question: Firestore multiple range query.
Solved it with this code:
let query;
category.queryParams.query.fieldFilters.forEach(function(fieldFilter) {
if (typeof query === "undefined") {
query = collection.where(fieldFilter.field, filterOperationString(fieldFilter.filterType), fieldFilter.filterValue);
} else {
query = query.where(fieldFilter.field, filterOperationString(fieldFilter.filterType), fieldFilter.filterValue);
}
});

Javascript object with arrays to search param style query string

Looking for clean way to convert a javascript object containing arrays as values to a search param compatible query string. Serializing an element from each array before moving to the next index.
Using libraries such as querystring or qs, converts the object just fine, but handles each array independently. Passing the resulting string to the server (which I cannot change) causes an error in handling of the items as each previous value is overwritten by the next. Using any kind of array notation in the query string is not supported. The only option I have not tried is a custom sort function, but seems like it would be worse than writing a custom function to parse the object. Any revision to the object that would generate the expected result is welcome as well.
var qs = require("qs")
var jsobj = {
origString:['abc','123'],
newString:['abcd','1234'],
action:'compare'
}
qs.stringify(jsobj,{encode:false})
qs.stringify(jsobj,{encode:false,indices:false})
qs.stringify(jsobj,{encode:false,indices:false,arrayFormat:'repeat'})
Result returned is
"origString=abc&origString=123&newString=abcd&newString=1234&action=compare"
Result desired would be
"origString=abc&newString=abcd&origString=123&newString=1234&action=compare"
I tried reorder your json:
> var jsobj = [{origString: 'abc', newString: 'abcd' }, {origString: '123',
newString: '1234' }, {action:'compare'}]
> qs.stringify(jsobj,{encode:false})
'0[origString]=abc&0[newString]=abcd&1[origString]=123&1[newString]=1234&2[action]=compare'
But I don't know if this is a good alternative for your problem.
Chalk this up to misunderstanding of the application. After spending some more time with the API I realized my mistake, and as posted above by others, order does no matter. Not sure why my first several attempts failed but the question is 'answered'

Figuring out the type of column holding null

I've read the answers to this question why is typeof null "object"? and I'm trying to figure out a solution to a specific problem.
I am working on a Web application with angularJs for the front-end and ASP.NET MVC for the server code. We're passing our models to the front-end and then updated values back. Here is the situation I'm trying to find a solution for:
When we're deleting numbers from the input type="number" using a backspace key in the Web interface, the value of the input control becomes null. In our models the numeric values are defined as not nullable (and in our database the strings also should be converted to empty values instead of null).
I am trying to figure out a way to set keys to 0 if they are numeric and to empty string if they are strings.
I've added a new method called verifyInput and here is the beginning of the code:
/**
* #returns true or false
* */
Model.prototype.verifyInput = function () {
for (let i = 0; i < this.keys.length; i++) {
const key = this.keys[i];
if (this[key] === null) {
this[key] =
}
}
}
I am not sure how to write the above method. I already found out that I can not use typeof or lodash methods to check for key's type as it's returning object. I need to set numbers to 0 and strings to empty strings, but leave datetime values alone. Do you see my problem and do you have ideas of a solution? Perhaps I should use try/catch here?
The idea I had of deleting these keys seem to work. I'm getting the correct behavior (e.g. the values automatically come as 0 in this case to the server).
I added the following code:
Model.prototype.verifyInput = function () {
let i = this.keys.length;
while (i--) {
const key = this.keys[i];
if (this[key] === null) {
this.keys.splice(i, 1); // Let's remove the null element from the model
}
}
return true;
}
based on another very helpful answer I found Looping through array and removing items, without breaking for loop and in the tests I made so far it worked nicely.

How can I create a URL encoded query string from an array of objects?

I found tons of examples of how to turn an objects keys/values into a URL encoded query string, but I need to use an array of objects and turn them into a query string.
I want to use pure JS or lodash to accomplish this task.
I am supplying an array of objects in this format:
[
{ points: "30.09404881287048,-96.064453125" },
{ points: "30.09404881287048,-94.63485717773439" },
{ points: "29.345072482286373,-96.064453125" },
{ points: "29.345072482286373,-94.63485717773439"}
]
I need it to be in this format:
points=30.09404881287048%2C-96.064453125&points=30.09404881287048%2C-94.63485717773439&points=29.345072482286373%2C-96.064453125&points=29.345072482286373%2C-94.63485717773439
I am currently accomplishing this using these two methods:
import { map as _map } from 'lodash';
const createQueryStringFromObject = object =>
_map(object, (value, key) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
const createQueryStringFromArray = array => array.reduce((queryString, object, index) => {
return queryString + createQueryStringFromObject(object) + (index < array.length - 1 ? '&' : '');
}, '');
I find the reduce function implementation sloppy though and I think it could be cleaner and more efficient. Any suggestions?
EDIT: I would like to keep the method generic so it can accept an array of objects with any key and not just specifically the key points. I also would like to keep the createQueryStringFromObject() method because I need it elsewhere on the site.
I like your solution, but I think just using built in prototype methods will clean up the code quite a bit:
list.map(p => `points=${encodeURIComponent(p.points)}`).join('&')
which results in
"points=30.09404881287048%2C-96.064453125&points=30.09404881287048%2C-94.63485717773439&points=29.345072482286373%2C-96.064453125&points=29.345072482286373%2C-94.63485717773439"
I am looking for a generic implementation that is not specific to any objects containing the key of points. Thanks to melpomene's suggestion to just use another map and join, I gave that a shot and I definitely like that format alot more. Much shorter and clear!
const createQueryStringFromArray = array =>
_map(array, object => createQueryStringFromObject(object))
.join('&');

Categories

Resources