type-challenges-solutions
type-challenges-solutions copied to clipboard
type-challenges-solutions/en/medium-chainable-options
Chainable Options
This project is aimed at helping you better understand how the type system works, writing your own utilities, or just having fun with the challenges.
https://ghaiklor.github.io/type-challenges-solutions/en/medium-chainable-options.html
Your solution is harder to read on hover in the IDE.. will probably exhaust it after a few steps
& { ... }
type Index = string | number | symbol
type Chainable<O extends { [key: Index]: unknown} = {}> = {
option<K extends Index, V>(key: K, value: V): Chainable<{ [key in keyof O | K]: key extends keyof O ? O[key] : V }>
get(): O
}
while hover over get invocation:
(method) get(): {
foo: number;
bar: {
value: string;
};
name: string;
}
@igalklebanov you can replace Index with PropertyKey type. It's built-in in TypeScript.
This solution cannot pass @ts-expect-error, am i right?
@dlehddnjs what exactly is the test case?
@ghaiklor It's the second test case. It does not allow duplicate keys with same type. This works.
type Chainable<O = {}> = {
option<K extends string, V>(key: K extends keyof O ? (V extends O[K] ? never: K) : K, value: V): Chainable<Omit<O, K> & { [P in K]: V }>;
get(): O;
};
We check whether the key is already present and if it is of the same type. If either condition fails, we add it to the object. The return type also needs to be updated so that we remove the previous instance of the key that is being added.
@AbePlays cool, thanks for the update. Yeah, I remember passing those tests back then without the additional check 😄
Because it was hard for me to understand each solution.
Here is the @AbePlays commented solution
type Chainable<O = {}> = {
option<K extends string, V>(
key: K extends keyof O ?
// Key already exist
(
V extends O[K]
// Value type is same of previous one
? never
// Value type is different of previous one
: K
) :
// Key not exist yet
K,
value: V
):
// Use Omit<O,K> instead only O To be sure O[K] value have the type of the last .option passed
Chainable<Omit<O,K> & {[P in K] : V}>
get(): O
}
@AbePlays The V extends O[K] check isn't needed to pass all test cases (as of Nov 16 2022).
type Chainable<O = {}> = {
option<K extends string, V>(key: K extends keyof O ? never : K, value: V): Chainable<Omit<O, K> & { [P in K]: V }>
get(): O
}