Converting Objects to Query Strings and Vice Versa

Hero image for Converting Objects to Query Strings and Vice Versa. Image by David Clode.
Hero image for 'Converting Objects to Query Strings and Vice Versa.' Image by David Clode.

In Brief

Use URLSearchParams to encode and parse ordinary query strings, then decide explicitly how your application represents repeated keys, arrays, empty values, and reconstructed types. Querystring values are strings, and duplicate keys carry meaning, so a convenient object conversion should not silently discard ordering or collapse several values into one.

As the lines between frontend and fullstack development continue to blur, more and more applications are written entirely with JavaScript from the 'actual' frontend to complete serverside APIs and everything in between. It has long been the case that, as frontend engineers, we need to be able to handle and react to query parameters in the URL to handle search parameters or persist state via the URL. Now and going forwards, more than ever, converting objects to query strings (and vice versa) is an essential skill.

In this article, I intend to explore how we can convert objects into query strings, how to parse query strings back into objects, and in particular how this applies when working with Next.js. By the end, you should have a solid understanding of handling query parameters effectively.


What is a Query String?

Starting with the absolute basics, a query string is a part of a URL which contains keyvalue pairs used to pass data between pages or to an API. It begins with a ? and separates parameters with &. For example:

https://example.com/search?query=JavaScript&page=2

In this URL, query=JavaScript and page=2 are parameters that can be read and used by the server or clientside application. Query strings are commonly used in APIs, search functionality, and clientside state management.


Converting an Object to a Query String

JavaScript provides several ways to construct query strings from objects, depending on the level of control needed.

Using URLSearchParams

The URLSearchParams API (which has been available in browsers for eleven years now) simplifies working with query strings:

const params = { search: "JavaScript", page: "2", sort: "asc" };const queryString = new URLSearchParams(params).toString();console.log(queryString);  // Output: "search=JavaScript&page=2&sort=asc"

This method automatically encodes special characters and produces a properly formatted query string. The record passed to the constructor contains strings; use append() when a key has more than one value.

Custom Function for More Control

If you need more flexibility, you could implement a custom function like this:

type QueryValue = string | number | boolean;type QueryObject = Record<string, QueryValue | QueryValue[]>;const objectToQueryString = (obj: QueryObject): string => {  const query = new URLSearchParams();  Object.entries(obj).forEach(([key, value]) => {    const values = Array.isArray(value) ? value : [value];    values.forEach((item) => {      query.append(key, String(item));    });  });  return query.toString();};const params = { search: "JavaScript", page: 2, sort: "asc" };console.log(objectToQueryString(params));// Output: "search=JavaScript&page=2&sort=asc"

Here, the function converts an object into a query string and uses append() for every value. Scalar values produce one pair, while arrays produce repeated keys without discarding their order.&. This provides greater control over the formatting, ensuring that special characters are encoded correctly and allowing for modifications such as handling arrays or nested objects differently.


Parsing a Query String into an Object

To extract parameters from a query string and convert them into an object, JavaScript also provides builtin utilities.

Using URLSearchParams

The simplest way to parse query strings is to use URLSearchParams again, like this:

const queryString = "search=JavaScript&page=2&sort=asc&tag=css&tag=javascript";const searchParams = new URLSearchParams(queryString);const params: Record<string, string | string[]> = {};searchParams.forEach((value, key) => {  const current = params[key];  if (current === undefined) {    params[key] = value;  } else {    params[key] = Array.isArray(current) ? [...current, value] : [current, value];  }});console.log(params);// Output: { search: "JavaScript", page: "2", sort: "asc", tag: ["css", "javascript"] }

The values are strings. In this example, repeated keys are represented as string arrays rather than silently retaining only the final value. Convert values to other types only when the application's input contract requires it.

Custom Function for Parsing

A small wrapper can define how repeated values are represented without reimplementing URL parsing and decoding:

type QueryObject = Record<string, string | string[]>;const queryStringToObject = (query: string): QueryObject => {  const searchParams = new URLSearchParams(query.startsWith("?") ? query.slice(1) : query);  const result: QueryObject = {};  searchParams.forEach((value, key) => {    const current = result[key];    if (current === undefined) {      result[key] = value;    } else {      result[key] = Array.isArray(current) ? [...current, value] : [current, value];    }  });  return result;};console.log(queryStringToObject("?search=JavaScript&page=2&tag=css&tag=javascript"));// Output: { search: "JavaScript", page: "2", tag: ["css", "javascript"] }

This keeps parsing and decoding with URLSearchParams, while the wrapper explicitly represents repeated keys as arrays. More complex structures still need a documented applicationspecific encoding contract.


Handling Query Strings in the Next.js Pages Router

In the Next.js Pages Router, query strings are commonly used for search parameters and URLbased state. The router exposes utilities for reading and updating them.

Accessing Query Parameters in Next.js

In the Pages Router, Next.js provides the useRouter hook, which you can use like this:

import { useRouter } from "next/router";type QueryValue = string | string[] | undefined;const firstQueryValue = (value: QueryValue) => {  return Array.isArray(value) ? value[0] : value;};const SearchPage = () => {  const router = useRouter();  const search = firstQueryValue(router.query.search);  const page = firstQueryValue(router.query.page);  const sort = firstQueryValue(router.query.sort);  return (    <div>      <h1>Search Results</h1>      <p>Search Term: {search}</p>      <p>Page: {page}</p>      <p>Sort Order: {sort}</p>    </div>  );};export default SearchPage;

Query parameters are available in router.query as an object, but as with using URLSearchParams each value can be a string, a string array, or undefined until the router is ready. The example selects the first value explicitly because the page expects one value per field.

Updating Query Parameters in Next.js

To update the URL with new query parameters without reloading the page, we can define a custom hook around router.push:

import { useRouter } from "next/router";type QueryValue = string | string[] | undefined;type QueryUpdates = Record<string, QueryValue>;const useUpdateQuery = () => {  const router = useRouter();    return (newParams: QueryUpdates) => {    void router.push(      {        pathname: router.pathname,        query: { ...router.query, ...newParams },      },      undefined,      { shallow: true },    );  };};

This allows us to modify the query string dynamically without causing a full page refresh.


Wrapping Up

Converting objects to query strings and parsing query strings into objects is a key technique in JavaScript, especially when working with URLs. The URLSearchParams API provides a simple way to handle this, but custom functions allow for greater flexibility.

In a Pages Router component, useRouter exposes string, stringarray and temporarily undefined values, while a custom hook can update the URL without a full page reload.

Key Takeaways

  • Query strings enable passing keyvalue data via URLs.
  • The URLSearchParams API simplifies converting objects to query strings and vice versa.
  • Custom functions provide more control, such as handling arrays and complex structures.
  • Query parameters in Next.js are accessed via router.query and modelled as string, string array, or undefined.
  • Updating query parameters in Next.js can be done using router.push() with shallow routing.

Understanding these techniques allows you to handle query parameters more efficiently, making it easier to manage search functionality and URLbased state in JavaScript and Next.js applications.


Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.