Decode URL search params at the type level with ts-toolbelt

TypeScript has really cool string interpolation powers. We want to take this following query:

const query = `/home?a=foo&b=wow` 

And turn it into an object like this:

const obj: Union.Merge<QueryParams> = {
  a: "foo",
  b: "wow",
}

This features autocomplete and if we change one of the query parameters, TypeScript will let us know that it's incorrect and give us autocomplete.

To accomplish this we start with creating a Query type and then splitting the query up on the ? character in order to get the query parameters. Make sure to grab the second element of the array because the first element is just the path.

type Query = typeof query

type SecondQueryPart = String.Split<Query, "?">[1]

Once we have the query split up, we'll split it again on each & character to get each individual query parameter.

type QueryElements = String.Split<SecondQueryPart, "&"> 

We are using the ts-toolbelt library to help us with the string manipulation.

Now we want to build our QueryParams type now that we have them all split up. First of all, we want each QueryElement. So we'll create a mapped type with [QueryElement in QueryElements[numbedr]].

type QueryParams = {
  [QueryElement in QueryElements[number]]: {}
}

Then we can create key value pairs of each parameter by mapping again over the split QueryElement and setting the key to the first element of the split and the value to the second element of the split.

type QueryParams = {
  [QueryElement in QueryElements[number]]: {
    [Key in String.Split<QueryElement, "=">[0]]: String.Split<
      QueryElement,
      "="
    >[1]
  }
}

And we then do this funny thing. that we've seen in previous tips. We iterate over each each member of the object, since we don't want the type to be the nested structure, just the key value pairs of the query params.

type QueryParams = {
  [QueryElement in QueryElements[number]]: {
    [Key in String.Split<QueryElement, "=">[0]]: String.Split<
      QueryElement,
      "="
    >[1]
  }
}[QueryElements[number]]

And finally we Union.Merge the QueryParams to merge them all into an object.

const obj: Union.Merge<QueryParams> = {
  a: "wonderful",
  b: "wow",
}

Transcript

0:00 Hello folks. Today I'm going to show you how cool TypeScript's string interpolation powers really are. We want to turn this query here, this kind of search params query into an object down here. Through this crazy web of TypeScripts, we managed to do it.

0:18 Here we should see that if we go a=wonderful, then the type down here is no longer correct. It gives us auto complete to wonderful. Let's go through each step and see how we've managed to do this. First of all, we've taken the query and just for fun purposes, we've taken a type of it. We're going to type of query.

0:37 Next up, the second query part we've basically split the query by here and we've taken the second part of it. If I remove the second part here, we've got string query part. We've split it by this point here. We've just said just grab the second bit of it.

0:55 Now we've split it again by the & here. We've got now a=wonderful and B=now. We are doing this by grabbing some utilities from TS tool belt, which is a very cool library. Next up, we've got these query params, which somehow we've managed to turn into a union type. We've got now a=wonderful and b=now.

1:16 What we do is we take query elements, which is a two pull here and we say, first of all, we want each query element. The query elements number here is a way of saying each one of these query elements. For each query element in that two pull, first of all we grab the key here.

1:34 That key is going to be the a for instance, there, so a=wonderful because we're splitting the string by the by the equals there and grabbing the first part, and then we say is the value. The value is then the same code there is we're grabbing the second part.

1:51 We then do this funny thing that we've seen in previous tips, where we then iterate over each member of that object. If we return this or grab it there, then you'd see it would be a=wonderful, b=now. Like this, for instance.

2:05 Whereas instead we grab that and we do that instead, and that means that we then just get a=wonderful, b=now. Then we union merge them, which is just merges all the elements of the union into an object.

TypeScript's string interpolation powers are incredible, especially since 4.1. Add some utilities from ts-toolbelt, and you've got a stew going.

Here, we decode some URL search params AT THE TYPE LEVEL.

Discuss on Twitter

More Tips

Play Type Predicates

Type Predicates

1 min

Play TypeScript 5.1 Beta is OUT!

TypeScript 5.1 Beta is OUT!

2 mins

Play How to Name your Types

How to Name your Types

4 mins

Play Don't use return types, unless...

Don't use return types, unless...

4 mins

Play TypeScript 5.0 Beta Deep Dive

TypeScript 5.0 Beta Deep Dive

6 mins

Play Conform a Derived Type Without Losing Its Literal Values

Conform a Derived Type Without Losing Its Literal Values

1 min

Play Avoid unexpected behavior of React’s useState

Avoid unexpected behavior of React’s useState

1 min

Play Understand assignability in TypeScript

Understand assignability in TypeScript

2 mins

Play Compare function overloads and generics

Compare function overloads and generics

1 min

Play Use infer in combination with string literals to manipulate keys of objects

Use infer in combination with string literals to manipulate keys of objects

1 min

Play Access deeper parts of objects and arrays

Access deeper parts of objects and arrays

1 min

Play Ensure that all call sites must be given value

Ensure that all call sites must be given value

1 min

Play Understand how TypeScript infers literal types

Understand how TypeScript infers literal types

1 min

Play Get a TypeScript package ready for release to NPM in under 2 minutes

Get a TypeScript package ready for release to NPM in under 2 minutes

1 min

Play Use assertion functions inside classes

Use assertion functions inside classes

1 min

Play Assign local variables to default generic slots to dry up your code and improve performance

Assign local variables to default generic slots to dry up your code and improve performance

2 mins

Play Know when to use generics

Know when to use generics

2 mins

Play Map over a union type

Map over a union type

1 min

Play Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig

1 min

Play Use generics to dynamically specify the number, and type, of arguments to functions

Use generics to dynamically specify the number, and type, of arguments to functions

1 min

Play Use 'declare global' to allow types to cross module boundaries

Use 'declare global' to allow types to cross module boundaries

2 mins

Play Turn a module into a type

Turn a module into a type

2 mins

Play Create autocomplete helper which allows for arbitrary values

Create autocomplete helper which allows for arbitrary values

2 mins

Play Use deep partials to help with mocking an entity

Use deep partials to help with mocking an entity

1 min

Play Throw detailed error messages for type checks

Throw detailed error messages for type checks

1 min

Play Create a 'key remover' function which can process any generic object

Create a 'key remover' function which can process any generic object

1 min

Play Use generics in React to make dynamic and flexible components

Use generics in React to make dynamic and flexible components

1 min

Play Create your own 'objectKeys' function using generics and the 'keyof' operator

Create your own 'objectKeys' function using generics and the 'keyof' operator

1 min

Play Write your own 'PropsFrom' helper to extract props from any React component

Write your own 'PropsFrom' helper to extract props from any React component

1 min

Play Use 'extends' keyword to narrow the value of a generic

Use 'extends' keyword to narrow the value of a generic

1 min

Play Use function overloads and generics to type a compose function

Use function overloads and generics to type a compose function

2 mins

Play Use 'in' operator to transform a union to another union

Use 'in' operator to transform a union to another union

2 mins

Play Derive a union type from an object

Derive a union type from an object

2 mins