Marius ROBERT
Marius ROBERT

Reputation: 505

URLSearchParams with multiple values

I have multiples URLSearchParams created from object mainly because it's lighter to write and to read but for some of them, I need multiple values for a "variable" like this : foo=bar&foo=baz.
For now I do it with .append but it's heavy to read having multiple lines pretty identical.
Is there a way to do it from the constructor, with an object ?

let params;

// Currently used (and working) code
params = new URLSearchParams()
params.append("foo", "bar");
params.append("foo", "baz");

console.log(params.toString()); //Wanted result but heavy to read with more values

// Wanted code (but not the wanted result for now)

params = new URLSearchParams({
    "foo": ["bar", "baz"]
});

console.log(params.toString());

params = new URLSearchParams({
    "foo": "bar",
    "foo": "baz"
});

console.log(params.toString());

Upvotes: 11

Views: 22741

Answers (5)

Drkawashima
Drkawashima

Reputation: 9762

You can create multiple params like this

const params = new URLSearchParams({a:1,b:2,c:3});
console.log(params.toString());

Or you can feed a single string with & to the constructor like this new URLSearchParams("a=1&b=2&c=3")


When you apply these params to an URL-object you could overwrite all params with url.search =. But if the url already has a param, it's better to not overwrite it and instead loop through your new UrlSearchParams object and add each param separately with url.searchParams.append

const params = new URLSearchParams("a=1&b=2&c=3");
console.log(params);

//You could apply them all to an url
const url = new URL("https://example.com");
url.search = params.toString();
console.log(url.toString())

//But if your url already contains a param, you gotta append new params separately
const url2 = new URL("https://example.com?x=1");
params.forEach((value, key) => { url2.searchParams.append(key, value); });
console.log(url2.toString())

Upvotes: 0

Garry Seldon
Garry Seldon

Reputation: 21

First process your data with the parseParam function

function parseParam(oldParams, newParams = {}, field = '') {
    for (const key in oldParams) {
        if (oldParams[key] !== null) {
            let field3 = field.length > 0 ? field + '[' + key + ']' : key ;
            switch (typeof oldParams[key]) {
                case 'object':         
                    let object = parseParam(oldParams[key], newParams, field3);
                    newParams = Object.assign(newParams, object);  
                    break;
                case 'string':
                case 'number':                        
                    newParams[field3] = oldParams[key];
                    break;
            }
        }
    }
    return newParams;
}

let query = {
    foo: ['bar', 'baz'],
    status: 3,
    products: {
        door: [4, 8],
        tile: 'not'
    }
};

const urlParams = new URLSearchParams(parseParam(query));
console.log(urlParams.toString());
// foo[0]=bar&foo[1]=baz&status=3&products[door][0]=4&products[door][1]=8&products[tile]=not

Upvotes: 1

にゃあ
にゃあ

Reputation: 161

console.log('' + new URLSearchParams(['bar', 'baz', 'qux'].map(v=>['foo', v])))

Upvotes: 1

jonrsharpe
jonrsharpe

Reputation: 122026

I would introduce a function to build the thing you need (URLSearchParams) from the structure you want (an object containing strings or arrays of strings), pushing the "heavy" details down below an interface you own. A simple implementation would just use .append as appropriate:

// For TypeScript (e.g. Angular) users:
// function createSearchParams(params: { [key: string]: string | string[] }): URLSearchParams {
function createSearchParams(params) {
  const searchParams = new URLSearchParams();
  Object.entries(params).forEach(([key, values]) => {
    if (Array.isArray(values)) {
      values.forEach((value) => {
        searchParams.append(key, value);
      });
    } else {
      searchParams.append(key, values);
    }
  });
  return searchParams;
}

console.log(createSearchParams({
  foo: ["bar", "baz"],
  hello: "world",
}).toString());

This provides an abstraction, so if you decide you'd rather use the init approach under the hood, you can do so, e.g.:

function createSearchParams(params) {
  return new URLSearchParams(Object.entries(params).flatMap(([key, values]) => Array.isArray(values) ? values.map((value) => [key, value]) : [[key, values]]));

}

console.log(createSearchParams({
  foo: ["bar", "baz"],
  hello: "world",
}).toString());

All of your tests will continue to pass, you don't have to change the consuming code. This is much neater than defining a function to convert the structure you want to the thing URLSearchParams needs, as shown in RenaudC5's answer.

Upvotes: 3

RenaudC5
RenaudC5

Reputation: 3829

The URLSearchParams can takes an init value as argument for its constructor containing the following:

One of:

  • A string, which will be parsed from application/x-www-form-urlencoded format. A leading '?' character is ignored.

  • A literal sequence of name-value string pairs, or any object — such as a FormData object — with an iterator that produces a sequence of string pairs. Note that File entries will be serialized as [object File] rather than as their filename (as they would in an application/x-www-form-urlencoded form).

  • A record of string keys and string values.

In your case the second one seems to work the best and can be done like this:

const searchParams = new URLSearchParams([['foo', 'bar'],['foo', 'baz'],['foo', 'qux']])

console.log(searchParams.toString())


If you want to deal with objects, you can create your own structure and use a function to create the wanted data

For example :

const params = [
  {name: "foo", values: ["bar", "baz", "qux"]},
  {name: "bar", values: ["foo", "foo2", "foo3"]},
]

const initParams = (params) => params.reduce((acc, curr) => {
  const arr = curr.values.map(x => [curr.name, x])
  return acc.concat(arr)
}, [])

const searchParams = new URLSearchParams(initParams(params))

console.log(searchParams.toString())

Upvotes: 15

Related Questions