Convert JavaScript Object to Query String (Recursively)

There’re many ways to convert a JS object to query string, But every method has its Pros & Cons.

In this article we will discuss each method and how you can use it so you have a full grasp of each method and when you need apply it.

Table of Contents

Using "URLSearchParams" Class

This method is used to transform a simple JS object that has a depth of 1 level of properties.

				
					const simple = {
  foo: "foo",
  bar: "bar",
  one: 1,
  two: 2
};

const queryString = new URLSearchParams(simple);

console.log(queryString.toString()); // "foo=foo&bar=bar&one=1&two=2"

				
			

We can also use it to decode a query string to its original object.

				
					const queryString = "foo=foo&bar=bar&one=1&two=2"

const simple = new URLSearchParams(queryString);
const obj = Object.fromEntries(simple.entries());

console.log(obj);

/* {
  bar: "bar",
  foo: "foo",
  one: "1",
  two: "2"
} */
				
			

Using Our Custom Script (Recommended)

This method is used to transform a complex JS object that has endless depth of levels of properties.

				
					function encode(object) {
	function reducer(obj, parentPrefix = null) {
		return function (prev, key) {
			const val = obj[key];
			key = encodeURIComponent(key);
			const prefix = parentPrefix ? `${parentPrefix}[${key}]` : key;


			if (val == null || typeof val === 'function') {
				prev.push(`${prefix}=`);
				return prev;
			}


			if (typeof val === 'boolean') {
				prev.push(`${prefix}=${val.toString().toUpperCase()}`);
				return prev;
			}


			if (['number', 'string'].includes(typeof val)) {
				prev.push(`${prefix}=${encodeURIComponent(val)}`);
				return prev;
			}


			prev.push(
				Object.keys(val).reduce(reducer(val, prefix), []).join('&')
			);
			return prev;
		};
	}


	return Object.keys(object).reduce(reducer(object), []).join('&');
}

const complex = {
  format: 'xlsx',
  modelClass: 'App\\Models\\User',
  columns: [
    'id',
    'name',
    'mobile',
    'email',
    'region.name',
    'hijri_created_at',
    'gregorian_created_at',
  ],
  with: [
    'region',
  ],
  filters: [{
    column: 'type',
    value: 'regionManager',
  },]
};

const queryString = encode(complex);

console.log(queryString);

// Output:

/*
"format=xlsx&modelClass=App%5CModels%5CUser&columns[0]=id&columns[1]=name&columns[2]=mobile&columns[3]=email&columns[4]=region.name&columns[5]=hijri_created_at&columns[6]=gregorian_created_at&with[0]=region&filters[0][column]=type&filters[0][value]=regionManager"
*/
				
			

We can also use it to decode a query string to its original object.

				
					function decode(querystring) {
    function parseValue(value) {
        if (value === "TRUE") return true;
        if (value === "FALSE") return false;
        return isNaN(Number(value)) ? value : Number(value);
    }

    function dec(list, isArray = false) {
        let obj = isArray ? [] : {};

        let recs = list.filter((item) => {
            if (item.keys.length > 1) return true;
            obj[item.keys[0]] = parseValue(item.value);
        });

        let attrs = {};
        recs.map((item) => {
            item.key = item.keys.shift();
            attrs[item.key] = [];
            return item;
        }).forEach((item) => attrs[item.key].push(item));

        Object.keys(attrs).forEach((attr) => {
            let nextKey = attrs[attr][0].keys[0];
            obj[attr] = dec(attrs[attr], typeof nextKey === "number");
        });

        return obj;
    }

    return dec(
        querystring
            .split("&")
            .map((item) => item.split("=").map((x) => decodeURIComponent(x)))
            .map((item) => {
                return {
                    keys: item[0]
                        .split(/[\[\]]/g)
                        .filter((n) => n)
                        .map((key) => (isNaN(Number(key)) ? key : Number(key))),
                    value: item[1],
                };
            })
    );
}

const querystring =
    "format=xlsx&modelClass=App%5CModels%5CUser&columns[0]=id&columns[1]=name&columns[2]=mobile&columns[3]=email&columns[4]=region.name&columns[5]=hijri_created_at&columns[6]=gregorian_created_at&with[0]=region&filters[0][column]=type&filters[0][value]=regionManager";

console.log(decode(querystring));

// Output:

/*
{
    format: 'xlsx',
    modelClass: 'App\\Models\\User',
    columns: [ 'id', 'name', 'mobile', 'email', 'region.name', 'hijri_created_at', 'gregorian_created_at' ],
    with: [ 'region' ],
    filters: [ { column: 'type', value: 'regionManager' } ]
}
*/
				
			
administrator
CEO of CoreCave, I have earned my stripes as a seasoned Senior Full-Stack Web Developer. I'm skilled in a wide range of front-end and back-end languages, responsive frameworks, and databases. My approach to coding is marked by a strict adherence to the best practices, grounding my work in functionality and efficiency. I strive to tackle real-world problems, improve systems, and make a tangible difference in our digital world.