Query String Parser
Challenge
You’re required to implement a type-level parser to parse URL query string into a object literal type.
Some detailed requirements:
- Value of a key in query string can be ignored but still be parsed to
true. For example,'key'is without value, so the parser result is{ key: true }. - Duplicated keys must be merged into one. If there are different values with the same key, values must be merged into a tuple type.
- When a key has only one value, that value can’t be wrapped into a tuple type.
- If values with the same key appear more than once, it must be treated as once. For example,
key=value&key=valuemust be treated askey=valueonly.
Solution
Zur Lösung dieser Challenge definieren wir zwei Hilfstypen Merge, welches zwei Objekte zusammenführt, so ParsePair, welcher Query-Parameter in einen Objekttyp umwandelt:
Kombiniert zwei Objekte zu einem Objekt. Sollte ein Attribut in beiden Objekten vorkommen, so wird ein Array mit allen Werten zurückgegeben.
// Merge<{foo:2}, {foo:3}> => {foo: [2,3]}
// Merge<{foo:2}, {gg: 4}> => {foo: 2, gg:4}
type Merge<T, U> = {
[K in keyof T | keyof U]: K extends keyof U
? K extends keyof T
? U[K] extends unknown[]
? [T[K], ...U[K]]
: T[K] extends U[K]
? T[K]
: [T[K], U[K]]
: U[K]
: K extends keyof T
? T[K]
: never
}
Wandelt die Query Parameter in Form von "T=U" in ein Objekt um. Fehlt der Wert (z.b. bei `k1`), so wird true als Wert zum Attribut zurückgegeben.
// 'k1=4' => { k1: 4}
// 'k1' => { k1: true}
type ParsePair<T extends string> = T extends `${infer K}=${infer V}`
? {
[P in K]: V;
}
: {
[P in T]: true;
};
Mit diesen beiden Hilfstypen muss man nun noch über die Zeichenkette mittels infer laufen, und die Werte erst in Objekttypen umwandeln,
und anschließend zusammenführen:
type ParseQueryString<T extends string> = T extends ""
? // Base-Case: Bei einem leeren String wird ein leerer Object-Typ returnt
{}
: T extends `${infer P}&${infer R}`
? //Finden wir einen Query-Parameter, so wandeln wir diesen in ein Objekt um, und mergen dieses Objekt mit allen weiteren Objekten, die durch den rekursiven Aufruf noch gefunden werden
Merge<ParsePair<P>, ParseQueryString<R>>
: ParsePair<T>;