Reputation: 9301
import encodeParam from './encodeParam'
interface UrlParam<T> {
name: string
defaultValue: T
encode: (value: T) => string | null
decode: (value: string) => T
}
function buildQueryString<T>(...params: [UrlParam<T>, T][]) {
const searchParams = new URLSearchParams()
params.forEach(([param, value]) => {
const newValue = encodeParam(param, value)
if (newValue) {
searchParams.set(param.name, newValue)
}
})
return searchParams.toString()
}
const one: UrlParam<string> = {
name: 'one',
defaultValue: '',
encode: (val) => val,
decode: (val) => val,
}
const two: UrlParam<number> = {
name: 'two',
defaultValue: 0,
encode: (val) => val.toString(),
decode: (val) => Number(val),
}
const query = buildQueryString(
[one, 'foo'],
[two, 42], // This will not compile because only UrlParam<string> is expected.
)
I've written a function called buildQueryString
to encode a series of query parameters using tuples that contain the definition of the param (UrlParam
) and the value that should be used. I am struggling to set up the signature of the method to accept a mixed type of UrlParam<T>
, how could this be accomplished?
This should of course work so that the [UrlParam<T>, T]
constraint of the tuple is still checked, so no any
types are used.
Upvotes: 1
Views: 103
Reputation: 3272
The key to typing functions like this is to make sure the generic type can contain all the information needed. With just a T
, everything gets converted to unions and we lose the ordering. Instead, we can use an array/tuple generic type (which I called A
) where each entry corresponds to each argument tuple:
type UrlParamPair<T> = [ UrlParam<T>, T ];
type UrlParamPairs<A extends any[]> = { [K in keyof A]: UrlParamPair<A[K]> };
function buildQueryString<A extends any[]>(...params: UrlParamPairs<A>) {
...
}
The UrlParamPairs
mapped (array) type converts the array of types to the array of tuples, e.g. UrlParamPairs<[string, number]>
becomes [ UrlParamPair<string>, UrlParamPair<number> ]
The type inference for the function call shows this working:
function buildQueryString<[string, number]>(
params_0: UrlParamPair<string>, params_1: UrlParamPair<number>): string
Upvotes: 1